C# 4.0中的协变和逆变

自从C# 2.0中泛型出现后,给C#的整个类型系统带来极大的方便,可以极大的减少代码的重复工作。例如,

class A<T>

{

  T Get(){}

  Set(T arg){}

}

这样我们可以在实例化A对象的时候确定A的真正类型,例如var a = new A<string>();,var b = new A<int>();这样在不破坏强类型系统的情况下,可以在调用当时候就确定类型,保证了类型的安全。但是泛型在继承问题上却带来很大的麻烦,例如:

public interface Base<T>

{

  T Get();

  void Set(T arg);

}

public class A<T>:Base<T>

{

  public string Get(){}

  public void Set(string arg){return "";}

}

上面代码中,A继承了Base,按到 父类的引用指向子类的对象的逻辑那么 Base<object> a = new A<string>();这样应该是没没有问题的,因为A就是Base的子类。但是注意了,这里其实是错误的,就连编译都不会编译通过的。为什么呢,因为A<string> 应该是派生自Base<string>而不是Base<Object>,Base<Object>和Base<string>是完全不同的类型,所以这里Base<object> a = new A<string>();是错误的。

这里我们来仔细分析下。假如Base<object> a = new A<string>();是成立的,那么就会出现这样的情况,Base<Object>的方法 Set(Object arg),将要被子类的A<string>的Set(string arg)来实现,也就是说,当我们调用Base<Object>.Set(Object arg)的方法的时候,实际上是被子类A<string>.Set(string arg)实现的方法,也就是调用的是A<string>.Set(string arg)方法,但是A<string>.Set(string arg)方法的参数是string类型,但是父类要求输入的是object,也就是说当我们发生Base<Object>.Set(Object arg)调用的时候,其实就是使用了A<string>.Set(string arg)这个实现,但是Base<Object>.Set(Object arg)的参数是object,实现类的确实A<string>.Set(string arg)string类型,这样就导致了从object到string的转换,这种转换不会隐式发生,而且是不安全的转换,所以编译器会直接报错

现在我们再来看看Base<Object>的方法Get (),这个方法返回的是object类型,他的子类的实现方法A<string>.Get()返回的是string,也就是我们当调用Base<Object>.Get的方法时候,其实用的是A<string>.Get()的实现,而且这个子类的方法返回的是string,Base<Object>.Get ()的返回的是object,string从object的转换是安全的,会自动隐式转换。所以是安全的。

从上面的分析来看。如果Base<T>中对T的使用都是用来做返回类型的,例如方法的返回类型,只读属性等,如下代码 1

Public Interface Base<T>

{

       T Pt{get;}

       T Get();

}

Public Class A<T>: Base<T>

{

       Public T Pt

{

Get {return object};

}

       Public T Get()

{

       Return object;

}

}

那么代码1 A<string>到Base<Object> 的转换就是安全的,因为string本身就是object的一个子类,那么这种情况下,Base<object> a = new A<string>();这样的方式应该是成立的,

但是往往实际使用中,T既用做了返回类型,也用做了输入类型,所以C#中直接拒绝了这种转换所以就导致了Base<object> a = new A<string>();报错不成立的情况。那么有没有办法做到呢,当然现在可以了,C#4.0中有了协变和逆变的出现,解决了这样的问题,但是同样也会做了很多的限制。

先说说什么是协变,什么是逆变,例如上面代码1例子中的Base<object> a = new A<string>();成立就是协变,其实也就类似 父类的引用指向子类的对象的逻辑,这在如果父类中对T的应用仅仅是做返回类型,这样的转换就安全的,就是协变。

什么是逆变呢,也同样用代码来说明把,例如:2

Public Interface Base<T>

{

       void Set(T arg);

       string Make(T arg);

}

Public Class A<T>: Base<T>

{

       Public void Set(T arg)

{

 

}

       Public string Make (T arg)

{

}

}

上面例子2中的T类型都是用做输入的类型,所以导致了Base<object> a = new A<string>();这样的转换是不可能的,是不安全的,但是反过来,Base<string> a = A<object>();这样的转换是成立的,也是安全的,这样的变化就叫做逆变,至于为什么安全,其实我们上面已经分析过了,我们这里再来单独分析下。

因为Base<object>. Set(object arg)的参数是object,但是他的实现类A<string>.Set(string arg)的方法参数是string,也就说调用Base<object>. Set(object arg)的时候,实际执行的是A<string>.Set(string arg)的代码,但是因为A<string>.Set(string arg)的参数是string,所以存在了object到string的不安全的转换,因此是不成立的。但是我们反过来,如果Base<string> a = A<object>();成立,我们分析下,调用Base<object>. Set(string arg)的方法的时候,其实执行的是A<object>.Set(object arg)方法,那么父类的string到子类的object转换是安全的,所以是成立的,这就叫做逆变。

 

到这里重点其实已经都说完了,C# 4.0中为了实现协变和逆变专门规定了两个关键字out 和in

举个协变的例子

Public Interface Base<out T>

{

       T Pt{get;}

       T Get();

       Void Set(T arg);//这段代码是错误的,以为T的规定是输入,也就是只能用于返回,因此这里用作输入是不正确的,编译器直接会报错

}

Public Class A<T>: Base<T>

{

       Public T Pt

{

Get {return object};

}

       Public T Get()

{

       Return object;

}

}

这样Base<Object> a = new A<string>();就是成立的,转换也是安全的,这就是协变。

在来看看逆变

Public Interface Base<in T>

{

       Void Set(T arg);

       T Get();//这里这样是不正确的,因为in只能用于输入,不能用于输入

}

Public class A<T>: Base<T>

{

       Public void Set(T arg)

{

}

}

那么Base<string> a = new A<Object>();这样的就是成立的,转换也是安全的,这个就叫做逆变,看着貌似是把object安全的转到了string,其实不是,看了上面的分析就知道,还是把string安全的转到了object。

如果要让协变和逆变都存在,那就同时使用out和in,其实这样的已经就不是规定了不变了。泛型主要就三种,协变,逆变和不变,协变和逆变上面说了,不变就是不给T进行任何约束,就是我们普通的一直的做法

Public Interface Base< T>这种做法就叫做不变,

Public Interface Base<in T,out S>这样就也存在协变,也存在逆变,如果T和S是同一个类型,其实,这已经就是不变了。

虽然out T只能用于返回类型,但是我们可以利用具有in类型的委托或者接口来做输入类型,例如:

public interface H<in T>

{

  void Set(T arg);

}

public interface Base<out T>

{

  T Get(H<T> arg);//这里我们就可以把out变成了输入类型使用。

  T Get(Action<T> arg);//这里用具有in的委托也可以。

}

 

最后注意,协变和逆变,也就是out 和in只能用在接口和委托上面。类,方法等上面是不能使用协变和逆变的。

posted @ 2017-04-25 00:49  HawkSharp  阅读(118)  评论(0)    收藏  举报