日期:2014-05-17  浏览次数:21107 次

C# Keywords Series 6 in(Generic Modifier)&out(Generic Modifier)

  本篇文章主要讲述 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>
    { }