标题:C#中泛型中的协变和逆变究竟是怎么一回事啊?
只看楼主
卡巴斯
Rank: 2
等 级:论坛游民
帖 子:50
专家分:31
注 册:2012-12-18
结帖率:100%
已结贴  问题点数:10 回复次数:1 
C#中泛型中的协变和逆变究竟是怎么一回事啊?
协变和逆变在获得派生类对象的时候到底是怎么一回事?协变中加个out是不是就是方法中的引用参数作用一样?逆变又是怎么一回事?
2013-04-07 22:12
yhlvht
Rank: 13Rank: 13Rank: 13Rank: 13
等 级:贵宾
威 望:36
帖 子:707
专家分:4405
注 册:2011-9-30
得分:10 
协变和逆变是NET Framework 4出来以后新增的特性
官方解释为:在 C# 和 Visual Basic 中,协变和逆变允许数组类型、委托类型和泛型类型参数进行隐式引用转换。 协变保留分配兼容性,逆变与之相反。
下面就用泛型为例来说明一下协变

//父类Base
class Base
{
}
//子类Derived
class Derived : Base
{
}

1.多态
static void Main(string[] args)
{
    Derived d = new Derived();
    Base b = d;
}
根据C#多态性,我们用父类引用b,指向子类对象d,这样是完全可以的

2.协变
static void Main(string[] args)
{
    IEnumerable<Derived> d = new List<Derived>();
    IEnumerable<Base> b = d;
}
我们现在使用了泛型
创建一个List数组,数组被指定里面只允许存放Derived类型的对象,因为List实现了IEnumerable接口,所以可以用IEnumerable的引用指向它(为什么要这么做呢,因为我们想用d.GetEnumerator()去枚举List<Derived>里面的每个对象)

现在我们想把这个IEnumerable<Derived>赋给父类的IEnumerable引用(我相信还是有人会问,为什么要这样做,因为Base类不仅仅只有一个子类,只是为了说明协变性,而只建了一个子类,转为父类引用以后,那么可以用b.GetEnumerator()枚举所有子类的对象,只要b指向的是不同的子类,就不需要像d.GetEnumerator(),e.GetEnumerator(),f.GetEnumerator()这样为每个子类都要写一句,这其实就跟多态类似),因为我们习惯了多态的用法,所以认为这样是理所当然的事情,但在以前,C#不这么认为,C#认为IEnumerable<T>这个泛型参数T是恒定的,也就是我们指定了T为Derived以后,就不可以在改为其它类型了

但现在Framework 4中有了协变,并且IEnumerable<T>接口的参数T,被规定为了协变类型参数,这就把不可能变为可能了,也就是我们认为在多态性的情况下理所当然的事,协变性做了与多态性类似的事情,这样看起来就很自然

协变和逆变只是一个概念,在Framework 4以前也存在,只是没有明确的概念
上面的例子只是说明以前不可以的事,现在可以了,协变和逆变其实远远没有这么复杂
下面是msdn上的例子
static object GetObject() { return null; }
static void SetObject(object obj) { }

static string GetString() { return ""; }
static void SetString(string str) { }

static void Test()
{
    // Covariance. A delegate specifies a return type as object,
    // but you can assign a method that returns a string.
    Func<object> del = GetString;    //协变

    //msdn上说,它们返回与委托类型指定的派生类型相比,派生程度更大的类型(协变)
    //也就是说,原本是要返回string类型的,现在转换成了派生程度更大的object类型

    // Contravariance. A delegate specifies a parameter type as string,
    // but you can assign a method that takes an object.
    Action<string> del2 = SetObject;    //逆变

    //msdn上说, 接受相比之下,派生程度更小的类型的参数(逆变)
    //也就是说, 原本是需要接受object类型,现在接受了更小的string类型的参数
}

out其实跟协变和逆变没有直接的关系
在参数前加out,意思是,这个参数假如在方法里面被改变了值,那么方法外面的参数变量值也会改变
static void Main(string[] args)
{
    int ii = 0;
    program p = new program();
    p.test(ii);
    Console.WriteLine(ii);
}

public void test(int i)
{
    i = 5;
}

在不使用out的情况下,我们将ii传入test方法,虽然方法中将值改变为了5,但是输出的时候仍然输出0

static void Main(string[] args)
{
    int ii = 0;
    program p = new program();
    p.test(out ii);
    Console.WriteLine(ii);
}

public void test(out int i)
{
    i = 5;
}
在使用out的情况下,在方法里面改变了参数的值,变量ii也改变了,你确实可以理解为传入了引用或是地址或是指针一类的东西

常见的用法是在一个方法需要返回两个值的时候使用out,也就是将out作为返回值来用

[ 本帖最后由 yhlvht 于 2013-4-8 10:11 编辑 ]
2013-04-08 08:55



参与讨论请移步原网站贴子:https://bbs.bccn.net/thread-403265-1-1.html




关于我们 | 广告合作 | 编程中国 | 清除Cookies | TOP | 手机版

编程中国 版权所有,并保留所有权利。
Powered by Discuz, Processed in 0.164914 second(s), 8 queries.
Copyright©2004-2024, BCCN.NET, All Rights Reserved