简介:

  前两篇文章讲了关于泛型的一些基础,下面笔者通过这篇文章来给刚刚接触泛型的朋友介绍一下

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

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

------------------------------------------------------------------------------------------------------------------------------------------------------------

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

|首先这2个拗口的名词先不用去管它,先知道协变和逆变主要是用在泛型的接口和委托上就可以了,下面我们通过一个例子来看看:

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

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

鸟和飞机都会飞,把飞定义成一个借口,在定义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<Cat> catArray = new List<Cat>();
List<Animal> animalArray = catArray;

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

继续往下看 这样写却可以

            IEnumerable<Cat> lCat = new List<Cat>();
IEnumerable<Animal> lAnimal = lCat;

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

概念引入:

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

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

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

(逆变)

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

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

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

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

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

概念引入:

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

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

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

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

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

下面我们演示一个例子

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

这里会出现一个错误

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

下面我们把 out 改成 in

这里会出现一个错误

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

这个演示充分说明了:out 修饰 T 则 T只能作为结果输出而不能作为参数  ; in 修饰 T 则 T只能作为参数而不能作为结果返回;

------------------------------------------------------------------------------------------------------------------------------------------------------------

<IEnumerable接口及其泛型版>

为什么要用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 = ; i < pArray.Length; i++)
{
_people[i] = pArray[i];
}
}
}

上面的代码我们定义了一个 Person 类和一个 People 类,显然 People是用来存放多个Person实例的集合,下面我们尝试用 Foreeach 遍历集合的每个元素 输出:

        static void Main(string[] args)
{
Person[] personArray = new Person[]{
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 = ; 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 = -; //是否可以往下 移
public bool MoveNext()
{
position++;
return (position < _people.Length);
} //集合的所有元素取完了之后 重置position
public void Reset()
{
position = -;
} //实现 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[]{
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<T> 支持 foreach 遍历 是因为它实现了IEnumerable接口和其泛型版,如图--

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

3. 从这两个接口的用词选择上,也可以看出其不同:IEnumerable是一个声明式的接口,声明实现该接口的class是“可枚举(enumerable)”的,但并没有说明如何实现迭代器,

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

关于IEnumerable<T>我们来了解一下它的代码:

4.由于IEnumerable<T>继承了IEnumerable接口,所以要实现IEnumerator<T> ,还需要实现IEnumerator接口,由于和泛型版本的方法同名,所以该方法的实现需要使用显式接口实现。这里就不继续介绍它的具体实现了,和IEnumerator基本一致,这里就不详述了,读者可以自己动手写一下。
 
ps 了解IEnumerable和IEnumerable<T>对今后学西理解LINQ是有很大帮助的。

出自: Keiling_J'Blog http://www.cnblogs.com/keiling/

C# -- 泛型(3)的更多相关文章

  1. 一起学 Java(三) 集合框架、数据结构、泛型

    一.Java 集合框架 集合框架是一个用来代表和操纵集合的统一架构.所有的集合框架都包含如下内容: 接口:是代表集合的抽象数据类型.接口允许集合独立操纵其代表的细节.在面向对象的语言,接口通常形成一个 ...

  2. .NET面试题系列[8] - 泛型

    “可变性是以一种类型安全的方式,将一个对象作为另一个对象来使用.“ - Jon Skeet .NET面试题系列目录 .NET面试题系列[1] - .NET框架基础知识(1) .NET面试题系列[2] ...

  3. C#4.0泛型的协变,逆变深入剖析

    C#4.0中有一个新特性:协变与逆变.可能很多人在开发过程中不常用到,但是深入的了解他们,肯定是有好处的. 协变和逆变体现在泛型的接口和委托上面,也就是对泛型参数的声明,可以声明为协变,或者逆变.什么 ...

  4. 编写高质量代码:改善Java程序的151个建议(第7章:泛型和反射___建议106~109)

    建议106:动态代理可以使代理模式更加灵活 Java的反射框架提供了动态代理(Dynamic Proxy)机制,允许在运行期对目标类生成代理,避免重复开发.我们知道一个静态代理是通过主题角色(Prox ...

  5. 6.在MVC中使用泛型仓储模式和依赖注入实现增删查改

    原文链接:http://www.c-sharpcorner.com/UploadFile/3d39b4/crud-operations-using-the-generic-repository-pat ...

  6. C#泛型详解(转)

    初步理解泛型: http://www.cnblogs.com/wilber2013/p/4291435.html 泛型中的类型约束和类型推断 http://www.cnblogs.com/wilber ...

  7. C# 泛型

    C# 泛型 1.定义泛型类 在类定义中包含尖括号语法,即可创建泛型类: class MyGenericClass<T> { //Add code } 其中T可以遵循C#命名规则的任意字符. ...

  8. java8中lambda表达式的应用,以及一些泛型相关

    语法部分就不写了,我们直接抛出一个实际问题,看看java8的这些新特性究竟能给我们带来哪些便利 顺带用到一些泛型编程,一切都是为了简化代码 场景: 一个数据类,用于记录职工信息 public clas ...

  9. java 泛型

    1.Student stu =tool.getObj();右边得到的是Object类型,需要向下转型,强转换. 2. 3. 4.泛型方法不能被静态修饰这样写 5.如果想定义定义静态泛型方法,只能这样写 ...

  10. Java泛型的历史

    为什么Java泛型会有当前的缺陷? 之前的章节里已经说明了Java泛型擦除会导致的问题,C++和C#的泛型都是在运行时存在的,难道Java天然不支持“真正的泛型”吗? 事实上,在Java1.5在200 ...

随机推荐

  1. laravel 中使用定时任务

    Laravel5.3 Artisan Console 文档地址 http://laravelacademy.org/post/6228.html 1.在服务器上查看定时任务有哪些crontab -e ...

  2. asp.net数据分页方法

    /// <summary> /// 数据分页方法 /// </summary> /// <param name="PageIndex">当前页& ...

  3. 【转】使用ant来调用Jmeter,并定制运行时参数

    为了应对不同的运行需求(主要是不同的线程数),以及可能的变化(host ip),在nongui运行时我对ant build.xml进行了一些修改 1. log目录备份与运行前清除 <tstamp ...

  4. VS2017自定义代码片段, 实现快捷输入

    点击VS2017的工具→代码片段管理器, 下图: 语言选择C#, 路径定位到 Visual C#, 然后复制这个路径在电脑中打开 这里以增加 crk 快捷方式输出 Console.ReadKey()来 ...

  5. select,poll,epoll,selectors

    一 了解select,poll,epoll IO复用:为了解释这个名词,首先来理解下复用这个概念,复用也就是共用的意思,这样理解还是有些抽象, 为此,咱们来理解下复用在通信领域的使用,在通信领域中为了 ...

  6. js控制使div自动适应居中

    一直都在想怎么样使弹出的DIV能在任何时候都是居中显示的,刚开始的时候是用CSS样式直接定义好层的位置,但是当页面很长的时候,或是浏览器窗口大小不是固定的时候就不能正确的显示,所以只好用JS来控制DI ...

  7. flask系列八之请求方法、g对象和钩子函数

    一.get方法 ,post方法 post请求在模板中要注意几点: (1)input标签中,要写name来标识这个value的key,方便后台获取. (2)在写form表单的时候,要指定method=' ...

  8. Oracle T4-2用jumpstart方式安装Solaris10

    在安装过程中遇到了2个问题 1) 安装时无法识别硬RAID磁盘 T4-2的2块本地盘做了硬RAID,用jumpstart安装时无法识别硬RAID磁盘,报错信息如下: {0} ok boot net - ...

  9. Java中 单例(Singleton)的两种方式

    第一种(饿汉式单例模式):在声明变量时实例化 public class Singleton { //静态初始化自动实例化 private static Singleton instance = new ...

  10. 使用JAVA实现模拟登陆并发送新浪微博(非调用新浪API)

    没有调用新浪的API,在程序中加入自己的帐号和密码就能发送微博,代码完全在后台运行,不用打开浏览器. 用了HtmlUnit这个库来模拟登录还有发送微博. 先上效果图: 这个是刚登陆上获取第一页的信息. ...