C#泛型(三)

网友投稿 706 2022-08-31

C#泛型(三)

C#泛型(三)

主要的内容:

<1>.原理性的东西----” 泛型的协变和逆变 “

<2>.以及常用的接口----” IEnumerable 及其泛型版的IEnumerable

<泛型的协变与逆变|泛型修饰符‘out’与‘in’>

先知道协变和逆变主要是用在泛型的接口和委托上就可以了,下面我们通过一个例子来看看:

我们知道接口是可以体现多态的,当然接口体现的多态注重的功能上的多态,这和抽象类不同,抽象类更注重的是建立在血缘关系上的多态。

知道接口是可以体现多态的之后,我们来看看一个相关的例子--

实例:鸟和飞机都会飞,把飞定义成一个接口,在定义2个类

public interface IFlyable { void fly(); } class Bird:IFlyable { public void fly() { Console.WriteLine("鸟儿飞!"); } } class Plane:IFlyable { public void fly() { Console.WriteLine("飞机飞!"); } }

下面看看接口体现的多态性:

IFlyable ifly; ifly = new Bird(); ifly.fly(); ifly = new Plane(); ifly.fly();

运行结果:

鸟儿飞!

飞机飞!

了解了接口的多态性后我们再来看一个例子:

这里定义了2个类 Animal 和 Cat (Cat继承了Animal)

public class Animal { } public class Cat:Animal { }

继续往下看:

Cat cat = new Cat();

下面这句代码,cat向animal转,子类向父类转换,这时cat会隐式转换为animal 我们说“儿子像父亲” 这是完全可以理解的

Animal animal = cat;

但是 说”父亲像儿子“ 这是说不过去的 ,但是有的时候如果儿子坑爹,强制转换了一下还是可以的

cat = (Cat)animal;

(协变)

List catArray = new List(); List animalArray = catArray;

如果是上面说的类,这样写是可以的,但是这里是会报错的  如图

继续往下看 这样写却可以:

IEnumerable lCat = new List(); IEnumerable lAnimal = lCat;

对 IEnumerable 转到定义 如图 我们发现这里多了一个 “out” 关键字

概念引入:

1.对于泛型类型参数,out 关键字指定该类型参数是协变的。 可以在泛型接口和委托中使用 out 关键字。“协变”是指能够使用与原始指定的派生类型相比,派生程度更大的类型。

--对于 “协变” 笔者是这样理解的就是”说的通变化“ 就像 “儿子像父亲一样”(假定父亲派生程度0那么儿子的派生程度就是1了,所以父亲可以使用派生程度更大的儿子)

协变与多态性类似,因此它看起来非常自然。

(逆变)

我们知道IComparable接口中,T的修饰符是‘in’,下面我们修改一下上面的代码演示一下

class Cat : Animal, IComparable { //仅演示 public int CompareTo(Cat other) { return 1; } } class Animal : IComparable { //仅演示 public int CompareTo(Animal other) { return 1; } }

这里Cat和Animal都实现了IComparable接口,然后我们这样写

IComparable ICat = new Cat(); IComparable IAnimal = new Animal(); ICat = IAnimal;

代码中ICat(高派生程度)使用 IAnimal(低派生程度) “父亲像儿子” 和上面的例子完全相反。

2.对于泛型类型参数,in 关键字指定该类型参数是逆变的。 可以在泛型接口和委托中使用 in 关键字。“逆变”则是指能够使用派生程度更小的类型。

--对于 “逆变” 笔者的理解则是 “坑爹儿子” 反过来硬说 “父亲像儿子” 这是 “说不过去的” 只是利用了强硬的手段

在了解了上面的内容后,我们来看看“out” 与 “in” 关键字的特性

IEnumerable接口的IEnumerator GetEnumerator()方法返回了一个迭代器 ,不难发现T如果用 out 标记,则T代表了输出,也就说只能作为结果返回。

下面我们演示一个例子

将动物会叫这功能,定义成一个泛型接口用 修饰

IComparable接口的CompareTo(T other)方法传入了一个T类型的Other参数,不难发现T如果用 in 标记,则T代表了输入,也就是它只能作为参数传入。

IComparable接口的CompareTo(T other)方法传入了一个T类型的Other参数,不难发现T如果用 in 标记,则T代表了输入,也就是它只能作为参数传入。

下面我们演示一个例子

将动物会叫这功能,定义成一个泛型借口用 修饰

这里会出现一个错误

把第二个带参数的setSound方法,去掉后编译可以正常通过

下面我们把 out 改成 in

这里会出现一个错误

把第一个setSound方法,去掉后编译可以正常通过,或者把第一个方法的返回值,改成其它非T类型,编译也可通过

这个演示充分说明了:

out 修饰 T 则 T只能作为结果输出而不能作为参数  ; in 修饰 T 则 T只能作为参数而不能作为结果返回;

为什么要用IEnumerable接口? 下面我们通过一个例子看看:

//定义Person类 public class Person { public Person(string _name) { this.name = _name; } public string name; } //定义People类 public class People { private Person[] _people; public People(Person[] pArray) { //实例化数组 用于存Person实例 _people = new Person[pArray.Length]; for (int i = 0; i < pArray.Length; i++) { _people[i] = pArray[i]; } } }

输出:

static void Main(string[] args) { Person[] personArray = new Person[3]{ new Person("Keiling1"), new Person("Keiling2"), new Person("Keiling3"), }; People people = new People(personArray); foreach (Person item in people) { Console.WriteLine(item.name); } }

这里编译不能通过,出现了一个错误

GetEnumerator:是IEnumerable接口中的一个方法,它返回一个 IEnumerator(迭代器),如下图

IEnumerator内部规定了,实现一个迭代器的所有基本方法,包括 如下图

为了在foreach中使用 People的实例, 我们给People实现IEnumerable接口,代码如下:

public class People:IEnumerable { private Person[] _people; public People(Person[] pArray) { //实例化数组 用于存Person实例 _people = new Person[pArray.Length]; for (int i = 0; i < pArray.Length; i++) { _people[i] = pArray[i]; } }

////IEnumerable和IEnumerator通过IEnumerable的GetEnumerator()方法建立了连接,可以通过IEnumerable的GetEnumerator()得到IEnumerator对象。 IEnumerator IEnumerable.GetEnumerator() { return (IEnumerator)GetEnumerator(); } public PeopleEnum GetEnumerator() { return new PeopleEnum(_people); } } public class PeopleEnum:IEnumerator { public Person[] _people; public PeopleEnum(Person [] pArray) { _people = pArray; } //游标 int position = -1; //是否可以往下 移 public bool MoveNext() { position++; return (position < _people.Length); } //集合的所有元素取完了之后 重置position public void Reset() { position = -1; } //实现 IEnumerator的 Current方法 返回当前所指的Person对象 object IEnumerator.Current { get { return Current; } } //Current是返回Person类实例的只读方法 public Person Current { get { try { return _people[position]; } catch (IndexOutOfRangeException) { throw new InvalidOperationException(); } } } }

测试运行:

static void Main(string[] args) { Person[] personArray = new Person[3]{ new Person("Keiling1"), new Person("Keiling2"), new Person("Keiling3"), }; People people = new People(personArray); foreach (Person item in people) { Console.WriteLine(item.name); } }

结果:

总结:

1.一个集合要支持foreach方式的遍历,必须实现IEnumerable接口,描述这类实现了该接口的对象,我们叫它 ‘序列’。

比如 List 支持 foreach 遍历 是因为它实现了IEnumerable接口和其泛型版,如图--

2. IEnumerator对象具体实现了迭代器(通过MoveNext(),Reset(),Current)。

而IEnumerator是一个实现式的接口,IEnumerator对象就是一个迭代器。

4.由于IEnumerable继承了IEnumerable接口,所以要实现IEnumerator ,还需要实现IEnumerator接口,由于和泛型版本的方法同名,所以该方法的实现需要使用显式接口实现。

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:为什么要内存对齐?Go 语言有时也需要考虑对齐的问题(内存对齐和字节对齐是一回事吗)
下一篇:ERP权限设置和CRM分析 (十二)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~