c#4.0中的不变(invariant)、协变(covariant)、逆变(contravariant)小记

简介: 不变/协变/逆变,4.0中的这几个概念越念越象绕口令,如果单纯死记硬背,就算记住了,时间长了还是会忘记的。 园子里已经有不少高手撰文写过这个话题:比如“装配脑袋”的NET 4.0中的泛型协变和反变 (2008年他就已经搞明白了这个概念)、偶像Artech的“C# 4.
不变/协变/逆变,4.0中的这几个概念越念越象绕口令,如果单纯死记硬背,就算记住了,时间长了还是会忘记的。
园子里已经有不少高手撰文写过这个话题:比如“装配脑袋”的 NET 4.0中的泛型协变和反变 (2008年他就已经搞明白了这个概念)、偶像Artech的“ C# 4.0新特性-"协变"与"逆变"以及背后的编程思 ” 以及1-2-3的 协变(Covariance)和逆变(Contravariance)的十万个为什么

这里只是从应用的角度,简单记录一下:
从.net3.5开始,System命名空间里就定义了一个泛型委托,原型如下:
public delegate TResult Func<T, TResult>(T arg);

即:输入一个泛型参数T,返回一个泛型结果TResult
假设有以下代码:
using System;
namespace in_co_contra_variant
{
    class Program
    {
        public static void Main(string[] args)
        {
            Func<object, ArgumentException> fn1 = Test;
            ArgumentException e1 = fn1("aaaaa");
            Func<string, Exception> fn2 = null;
            fn2 = fn1;
            Exception e2 = fn2("bbbbb");
            Console.WriteLine("e1:{0}\te2:{1}", e1.Message, e2.Message);
            Console.Read();
        }

        public static ArgumentException Test(object obj) 
        {
            return new ArgumentException(obj.ToString());
        }        
    }
}
在.net3.5环境下编译会报错:11行 fn2=fn1 这里会提示
Cannot implicitly convert type 'System.Func<object,System.ArgumentException>' to 'System.Func<string,System.Exception>' 
即:无法隐式将System.Func<object,System.ArgumentException>转换成System.Func<string,System.Exception>
说得更白一点,4.0以前的泛型委托,泛型参数一旦在实例使用过程中明确为具体类型后,是不能隐式自动转换成其它类型的,哪怕类型是兼容的(按道理来讲,fn1中的输入参数类型为object,由于string是继承自object的,所以能用object的地方,string应该是能用的;同理:fn1中(返回)输出参数类型ArumentException继承自Exception,所以返回类型ArgumentException可以向上的转化为Exception不会有任何问题,所以说fn1中的参数类型与fn2中的参数类型是安全兼容的,但是编译回不允许),这种不允许泛型参数类型变化的特点,称为 不变 (invariant).

而在4.0中,上面的代码可正常编译运行,如果研究下4.0中Func中的原型,会发现多了二个关键字:
public delegate TResult Func<in T, out TResult>(T arg);
即:在输入参数T前加了一个 in,而在输出参数(也就是返回参数)前加了一个 out.
这样编译器就能自动将T隐式转化为T的子类,而返回类型TResult也能自动隐式转化为它的父类。
说穿了就是OOP中的一个常理:子类与父类的继承关系,其实就是is a的关系,所以任何能用父类做为输入参数的地方,当然也能用子类作为替换(子承父业);而任何返回子类的地方,当然也能安全的向上转行为父类.(儿子是人类,父母当然也是人类,不可能是畜生,呵)
这时,我们称T为 逆变(ContraVariant)量,而TResult则为 协变(CoVariant)量。记忆方法:向上转型称协变(因为这种转型肯定是安全的,比较“和谐”),向下转型称逆变(因为不一定能转型成功,有出错的可能,称逆变)
最后:in,out这二个关键字不仅能用于泛型委托,同样也适用于泛型接口(比如4.0中的IEnumerable接口)
    public interface IEnumerable<out T> : IEnumerable
    {
        
        IEnumerator<T> GetEnumerator();
    }
目录
相关文章
|
3月前
|
安全 C#
C#进阶-协变与逆变
我们知道子类转换到父类,在C#中是能够隐式转换的。这种子类到父类的转换就是协变。而另外一种类似于父类转向子类的变换,可以简单的理解为逆变。逆变协变可以用于泛型委托和泛型接口,本篇文章我们将讲解C#里逆变和协变的使用。逆变和协变的语法第一次接触难免感到陌生,最好的学习方式就是在项目中多去使用,相信会有很多感悟。
38 0
|
8月前
|
Java
Java泛型的协变和逆变
Java泛型的协变和逆变
45 0
|
C# 开发者
C#——协变逆变
C#——协变逆变
72 0
|
Scala 开发者
协变逆变和不变 | 学习笔记
快速学习协变逆变和不变
46 0
|
安全 Java 编译器
Java泛型的协变与逆变
ava作为一门面相对象的语言,当然是支持面相对象的三大基本特性的,反手就蹦出三个词:封装、继承、多态。
95 0
|
编译器 C#
C#中的协变和逆变
C#中的协变和逆变
91 0
|
Kotlin
kotlin 泛型-协变、逆变
在java中,假设有一个泛型接口 GenericClass , 该接口中不存在任何以 T 作为参数的方法,只是方法返回 T 类型值: 那么,在 GenericClass 为此,我们必须声明对象的类型为 GenericClass
555 0
|
SQL 开发框架 .NET
|
安全 C# 编译器