打印

C#的协变和逆变

[复制链接]
1544|6
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
Simon21ic|  楼主 | 2016-6-13 07:06 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 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(){....};
}



相关帖子

沙发
Simon21ic|  楼主 | 2016-6-14 10:21 | 只看该作者
yyy71cj 发表于 2016-6-13 21:00
你把C#学到这份上,我得佩服你。
我用C#编了那多程序,也没研究到这份上。我一般是先用,后学 ...

懒得去记那么多规则,其实只要了解原理,都不用记规则了
只是用的话,那就太简单了

使用特权

评论回复
板凳
cuya| | 2016-6-16 23:13 | 只看该作者
C# 中的子类与父类机理与 c++ 大同小异。  C#中使用了单亲继承, 加入了interface,避免了C++ 中多重继承中的弊病

使用特权

评论回复
地板
dong_abc| | 2016-6-16 23:37 | 只看该作者
C# 程序需要.net框架才能运行让人很不爽。

使用特权

评论回复
5
cuya| | 2016-6-17 09:56 | 只看该作者
dong_abc 发表于 2016-6-16 23:37
C# 程序需要.net框架才能运行让人很不爽。

在 windows xp 下确实不爽,  xp 仅自带 dotnen 1.0, 毫无用处。但 windows 7 以后都自带 .net 3.5 以上, 就不是问题了。

使用特权

评论回复
6
Simon21ic|  楼主 | 2016-6-17 20:06 | 只看该作者
本帖最后由 Simon21ic 于 2016-6-17 20:07 编辑
cuya 发表于 2016-6-17 09:56
在 windows xp 下确实不爽,  xp 仅自带 dotnen 1.0, 毫无用处。但 windows 7 以后都自带 .net 3.5 以上 ...

现在电脑用着用着,就一堆的各种版本.net RT,最近学了一下,不过最终可能还是放弃,还是使用CLI程序+UI前端,CLI就用C语言+各种通用组件,比如SDL2啥的,UI还是先用lazarus,以后再换其他的

使用特权

评论回复
7
keer_zu| | 2016-6-22 20:57 | 只看该作者
还是喜欢java

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

266

主题

2597

帖子

104

粉丝