C#中的协变性/逆变性

7
我有一本书(链接),其中解释了协变性/逆变性如下:

  • 委托可以具有比其方法目标更具体的参数类型。这被称为逆变性。
  • 委托的返回类型可以比其目标方法的返回类型不那么具体。这被称为协变性。

以下是一个示例。

using System;

delegate void StringAction(string s);
delegate object ObjectRetriever();

class Test
{
    static void Main()
    {
        StringAction sa = new StringAction(ActionObject);
        sa("hello");

        ObjectRetriever o = new ObjectRetriever(RetrieveString);
        object result = o();
        Console.WriteLine(result);
    }


    static string RetrieveString() {return "hello";}

    static void ActionObject(object o)
    {
        Console.WriteLine(o);
    }
}

我认为为了使用协变性/逆变性,需要像示例中所示使用new,但我似乎使用sa = ActionObjecto = RetrieveString得到了相同的结果。(我在Mono上进行了测试)。
  • 那么,为什么作者要使用new来解释协变性/逆变性呢?
  • 协变性/逆变性背后的理论是什么?它只是一个描述object x = Everything inherit from object的花哨名称吗?这个奇怪的名字从哪里来?它的用途是什么?

1
我认为这篇帖子非常有用——https://dev59.com/VnI95IYBdhLWcg3wtwb4 - prosseek
4个回答

23
我有一本书,其中解释了协变性/逆变性如下...
那不是一个很好的协变性解释。它完全没有明确说明“协变性”和“逆变性”到底指的是什么。
实际上是可变的东西从未被提及。逆变性所涉及的是“从类型映射到具有该类型参数的委托”的映射。逆变性是映射和关系的属性。
试着阅读这个,看看你是否更好地理解了。

http://blogs.msdn.com/b/ericlippert/archive/2007/10/19/covariance-and-contravariance-in-c-part-three-member-group-conversion-variance.aspx

我认为为了使用协变性/逆变性,需要像示例中一样使用new,但是我似乎得到了相同的结果...
自C# 2.0以来,您可以说"d = M"或"d = new D(M)"--编译器只是将它们识别为两种不同的写法。
为什么作者要使用new来解释协变性/逆变性?
我不知道。
协变性/逆变性理论背后的原理是什么?
理论是,如果您有一个“排序”关系——也就是说,如果说X x = (Y)y是合法的,则X比Y大——并且您有一个保留排序关系的映射,则该映射是协变的。如果它颠倒了排序关系,则是逆变的。
例如,假设Animal是一个比Giraffe更大的类型。因此,您可以将类型为Giraffe的对象分配给类型为Animal的变量。Animal > Giraffe。
现在从类型T到一个接受T的方法M和一个委托类型D的映射。
您可以将一个接受Animal的方法M分配给类型为D的接受Giraffe的变量。D(Giraffe) > M(Animal),但Animal > Giraffe。关系被反转;映射是逆变的。

这只是一个花哨的名称来描述对象x = Everything inherit from object吗?

不是。它与该概念相关,因为对象是比几乎所有其他类型更大的类型。但实际上变体的是保留或反转大小关系的映射。

试着阅读这个看看是否有帮助。

http://blogs.msdn.com/b/ericlippert/archive/2009/11/30/what-s-the-difference-between-covariance-and-assignment-compatibility.aspx

这个奇怪的名字从哪里来?

范畴论。


1
很遗憾,您的博客链接不再按预期工作。 - Adrian K

2
我所见过的关于C#中协变性/逆变性的最好描述是Eric Lippert的一系列博客文章,可以在这里找到。从列表底部开始阅读,共有11个部分。
有时候阅读起来可能会有些困难,但它解释了你在一开始可能会问的所有问题。 :)
该文章是在实际的C# 4.0实现之前编写的,因此对语法的讨论已经过时,但其他所有内容似乎都按照描述实现了。

+1 我刚刚在阅读他关于这个主题的前几篇文章。 - csano

1

这里有一篇很棒的维基百科文章:这里

更多是关于漏斗倾斜方向和设置适当宽度的问题...


-1

我不明白你在第一个问题中的意思。

第二个问题可以通过建议查看强制转换来回答。逆变是允许将具有扩展类B的类A存储为类B。

逆变

class A {
}

class B : public A {
}

B obj = new A();

协变性是允许将扩展类A的类B存储为类A。

协变性

class A {
}

class B : public A {
}

A obj = new B();

你是完全错误的。协变性/逆变性涉及到泛型类型。 - Ivan Danilov
@Suroot:我想问一下为什么作者使用 ObjectRetriever o = new ObjectRetriever(RetrieveString); 而不是 ObjectRetrieve o = RetrieveString - prosseek
1
@Ivan 其中一种实现方式是泛型,但它涉及到更广泛 -> 更窄、更窄 -> 更宽的数据类型的通用转换。我看到这种情况最常见的地方是在返回类型中。注意:这不是唯一的情况,但(在我看来)通常是最广泛使用的形式。 - Suroot
1
伊万是正确的。你所描述的绝对不是方差。赋值兼容性是与方差相关的排序关系,但变量是一种保留或反转该关系的映射。如果您想了解赋值兼容性方差之间的区别,请参阅我在此主题上的文章:http://blogs.msdn.com/b/ericlippert/archive/2009/11/30/what-s-the-difference-between-covariance-and-assignment-compatibility.aspx - Eric Lippert
@Ivan:方差对C#有很多不同的影响,其中只有一种——泛型类型方差——专门处理泛型类型映射。原帖提问的问题是关于从方法组到委托的变体转换。C#还有数组类型的变体转换。像C++这样的语言具有虚拟方法实现的返回类型协变,而其他一些语言具有虚拟参数逆变性。有很多方式可以使事物具有变异性,而不涉及泛型。 - Eric Lippert
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接