本帖最后由 Simon21ic 于 2016-6-13 08:18 编辑
最近开始学习C#,大概已经有2-3天了,昨天买了一本《果壳中的C#5.0》,基本很快可以把基础的部分看明白,不过关于协变和逆变倒是花了将近1小时时间,不过也终于整明白了。这里就把这个的来龙去脉理一遍。
不过要说明的是,对于C#,我只是初学者,只是看了几天而已,有啥说了不对的,还望指正。
1. 首先中类的隐式转换说起
public class parent
{
}
public class child : parent
{
}
这里定义了2个类,其中,parent是基类;child是子类。
子类的应用可以直接赋值给基类应用:
child c = new child;
parent p = c;
new child后,生成child对象,位于托管堆中,内存的layout兼容parent对象,也就是说,child对象中,先放parent的字段,然后在放child的字段。并且,对象的TypeHandle指向的类型中的方法表也是一样,先放parent,在放child。所以,可以直接把child对象的应用,赋值给parent基类对象的引用,而不需要额外的开销。当然,p.GetType得到的,还是child类型,因为TypeHandle指向的还是child类型。这样的结果是:安全地把child类的实例当做parent类的实例来使用。也就是说,可以安全地把子类对象当做基类对象来处理。但是反过来不行,除了类型数据不匹配外,内存中的基类对象以及基类的方法表中,也没有子类的内容。
2. 泛型以及变化
这个功能上类似于C++中的模板,当然C语言里也可以使用宏来实现模板。
这里就只说泛型应用于接口:
public interface Iface<T>
{
public void F1(T obj){....};
public T F2(){....};
}
类似类的隐式转换一样,如果可以吧Iface<child>转换为Iface<parent>的话,那就可以具备协变性。
也就是说,可以把Iface<child>的“实例”(关闭类型,这里吧开放类型到关闭类型的转换也当做是一种实例化)当做Iface<parent>来使用。
首先写下Iface<child>的关闭型:
public interface Iface<child>
{
public void F1(child obj){....};
public child F2(){....};
}
其中,“实例”child F2()可以当做parent F2()来使用,因为返回值child的引用,可以隐式转换为parent的引用(子类可以兼容基类)。
但是,void F1(child obj)不能当做void F1(parent obj)来使用,因为parent obj引用不能赋值给child obj引用(基类无法保证安全兼容子类)。
不过,相反,“实例”void F1(parent obj)却可以当做void F1(child obj)来使用,因为如果传入child obj参数给“实例”的话,可以隐性转换为parent obj。
这里,就可以引出协变和逆变了
3. 协变和逆变
public interface Iface_out<out T>
{
public T F2(){....};
}
public interface Iface_in<in T>
{
public void F1(T obj){....};
}
很多地方说out和in的意思是类似输出和输入,只是在这种比较简单的情况下的情况。
按照之前说的,child F2()的“实例”,可以当做parent F2()来使用,这个就具备协变性(子类实例当做基类实例使用)
而void F1(parent)可以当做void F1(child)来使用,这个就是逆变性(基类实例当做子类实例使用)
也就是说,判断依据并不是输入或者输出,而是看
1. 子类实例是否可以安全当做基类实例使用 决定是否是协变
2. 基类实例是否可以安全当做子类实例使用 决定是否是逆变
public interface Iface_in<in T>
{
public void F2(T obj){....};
}
public interface Iface_out<out T>
{
public void F1(Iface_in<T> obj){....};
}
那么以上代码如何理解?是否正确呢?
这里,T都放在函数的参数里了。
对于Iface_out中的T为协变,所以:
public void F1(Iface_in<child> obj)这个子类“实例”可以安全当做public void F1(Iface_in<parent> obj)这个父类“实例”来使用
也就是说,如果传给这个子类“实例”函数的参数为Iface_in<parent>的引用,是安全的
也就是说,Iface_in<parent>的引用,可以安全地当做Iface_in<child>的引用来使用
也就是说,对于Iface_in中的T,基类实例可以安全地当做子类实例来使用,那就要求iface_in中的T是逆变,所以这个代码没问题
那么以下代码呢?
public interface Iface_in<in T>
{
public void F2(T obj){....};
}
public interface Iface_out<out T>
{
public Iface_in<T> F1(){....};
}
|