C#的协变和逆变

协变和逆变以及不变都是基于引用类型来讲的,我们一步一步的说起。
首先,面向对象设计有一个准则是“里氏替换原则”,这个原则是在讲“基类出现的地方,可以用子类来替换。”处于演示的目的,我们创建两个类,并让一个类继承于另一个类。让我们通过C#的例子来看一下:

 public class BaseClass
    {  //基类
       // 处于简化的目的,这里并没有任何实现
    }

 public class DerivedClass : BaseClass
    {
       //子类 
    }
    .....................
 static void Main(string[] args)
        {
            DerivedClass derivedClass=new DerivedClass();
            BaseClass baseClass = Test(derivedClass);
            Console.ReadLine();
        }

 public static BaseClass Test(BaseClass baseClass)
        {
            return new DerivedClass();
        }

在这个例子总,我们定义了一个Test方法,该方法接受一个BaseCLass类型的参数,输出一个DerivedClass类型的值。
先看一下这个方法的输出类型:是一个BaseClass,我们可以在方法内部返回一个DerivedClass,编译器会正常通过,说明实现的类型转换是安全的,实际上就是安全的:我们用一个BaseClass类型的变量去引用该方法的返回值,这个是一个安全的向上转换。这个是叫做输出类型的协变性。在来看一下该方法的输入参数类型:是一个BaseClass,我们可以在调用这个方法的时候放进去一个子类,编译器也不会报错,也是一种安全的类型向上转换。这个是叫做输入类型的逆变性,协变性和逆变性本质上都是符合“里氏替换原则”的。但是在C#中,对于协变和逆变的支持是有限的,比如,我们定义一个接口,然后用一个类来实现这个接口:

//缺乏协变的返回类型
public interface ICloneable{
object Clone();
}

我们如果要定义一个类来实现这个接口,比如我们定义一个Person类:

public class Person:ICloneable{
public Person Clone(){
//编译器会提示未实现接口方法
}
}

我们定义的这个Person类实现的Clone方法想返回一个Person,这个本来是很符合逻辑的一件事,但是C#不允许我们这么做。参数也存在类似的问题。 假定一个接口方法或一个虚方法, 其签名是void Process( string x),那么在实现或者覆盖这个方法时, 使用一个放宽 了 限制的签名应该是合乎逻辑的, 如 void Process( object x)。 (之所以合乎逻辑,是因为可以在要求输入基类的输入参数位置放进去一个子类)这称为参数类型的逆变性。关于解决的方案可以在C# in depth中查找。这里因为篇幅的原因,略去。

委托上的协变和逆变

逆变:委托的逆变可以通过windows forms程序来看:

public delegate void EventHandler( object sender, EventArgs e) 
public delegate void KeyPressEventHandler( object sender, KeyPressEventArgs e) 
public delegate void MouseEventHandler( object sender, MouseEventArgs e)

这三个委托在windows forms中用来定义一些事件。他们包含的参数中的第二个参数都是继承自EventArgs类的,我们定义如下方法:

public static void LogPlainEvent(object sender,EventArgs e){
Console.WriteLine("Event ocuured!");
}

接下来,我们就可以将这个事件处理方法注册到相关事件上:

Button button = new Button(); 
button. Text = "Click me"; 
button. Click += LogPlainEvent; //❶ 使用方法组转换 
button. KeyPress += LogPlainEvent; //❷ 使用转换和逆变性 
button. MouseClick += LogPlainEvent;

可以看到,委托是可以享受到逆变带来的便利的。
接下来我们看一下委托的协变:

public delegate Stream StreamFactory(); //❶ 声明 返回 Stream 的 委托 类型 static MemoryStream GenerateSampleData() //❷ 声明 返回 MemoryStream 的 方法 
{ 
byte[] buffer = new byte[ 16];
for (int i = 0; i < buffer. Length; i++)
 {
 buffer[i]=(byte)i;}
return new MemoryStream( buffer); } 
... 
StreamFactory factory = GenerateSampleData; //❸ 利用 协 变性 来 转换 方法 组 
using (Stream stream = factory()) //❹ 调用 委托 以 获得 Stream 
{ int data; 
while ((data = stream. ReadByte()) != -1) 
{ Console. WriteLine( data); }
}

可以看到返回类型为MemoryStream的方法组可以转换为返回类型为Stream的StreamFactory委托。

C#4及以后的委托

内建的Action<in T,....in Tn>Func<in T,out TResult>是在C#4出现的,包括泛型接口和泛型委托,这种以in和out来定义的限制的协变和逆变。使用情况和上面介绍的一样。以上面定义的BaseClass和DerivedClass来举例的话,Action<DerivedClass>=Action<BaseClass>体现的是逆变,Func<DerivedClass,BaseClass>=Func<BaseClass,DerivedClass>体现的是输入类型的逆变和输出类型的协变。但这和真正的逆变和协变不是一回事。
C#还没有实现。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值