日期:2014-05-17 浏览次数:21107 次
本篇文章主要讲述 in 和 out 两个泛型参数修饰符。对于这两个关键字,楼主也是各种不懂,要学会使用这两个关键字,必须先理解协变和逆变的概念。
协变和逆变
简单定义就是:协变就是泛型接口从子类向父类转化,逆变就是父类向子类转换。下面两种转换就是协变和逆变:
IEnumerable<string> strs = null; IEnumerable<object> objs = null; objs = strs; // “子类”向“父类”转换,即泛型接口的协变 strs = objs; // “父类”向“子类”转换,即泛型接口的逆变,这个会报错,IEnumerable 接口用 out 修饰泛型参数,限定了只能协变,这里只是为了说明两个转换的区别
上面的代码,IEnumerable<string> 和 IEnumerable<object> 表面上看有点类似父类和子类的关系,实际上他们根本没有任何继承关系,可以认为是两个独立的类。而 .NET 4.0开始,有条件的允许上面协变和逆变的转换,这里就用到 in 或 out 关键字修饰泛型参数 T 的使用范围。
可以看下 IEnumerable 的声明
[TypeDependency("System.SZArrayHelper")] public interface IEnumerable<out T> : IEnumerable { IEnumerator<T> GetEnumerator(); }
如果 IEnumerable 接口用 in 修饰泛型参数的话,那么逆变(即 strs = objs;) 是可以通过编译的。所以我们总结起来就是: 用 out 来修饰泛型参数时允许协变,用 in来修饰泛型参数允许逆变。
说到这里,这两个关键字倒没什么讲的,那为什么.NET 4.0开始引入协变逆变。
List<object> list = new List<object>(); list.AddRange(strs); // 如果没有协变(子类对于父类的隐式转换),这行代码会报错 list.AddRange(objs);
上面代码,如果IEnumerable 接口没有用 out 修饰泛型参数,那就会编译报错。而且你得写个循环,将 strs 一个个转换为 IEnumerable<object>,为了避免这样的麻烦,支持协变的话,就可以直接转换,不会编译报错。所有协变逆变的问题实际上都源于一个根本的原则: 子类可以向父类隐式转换,父类不能向子类隐式转换。
最后再看一个完整的例子,来看看 in 和 out 的使用方法:
class Program { static void Main(string[] args) { IContravariant<Object> iobj1 = new Sample<Object>(); IContravariant<String> istr1 = new Sample<String>(); ICovariant<Object> iobj2 = new Sample<Object>(); ICovariant<String> istr2 = new Sample<String>(); istr1 = iobj1; // in 修饰符实现协变(子类对于父类的隐式转换) iobj2 = istr2; // out 修饰符实现逆变(父类对于子类的隐式转换) } } // 逆变接口. interface IContravariant<in A> { } // 协变接口. interface ICovariant<out R> { } // 实现协变逆变接口的类. class Sample<A> : IContravariant<A> ,ICovariant<A> { }