为什么“Covariance”和“Contravariance”的概念在实现接口方法时适用?

时间:2010-03-23 17:41:29

标签: c# .net covariance contravariance

用例是这样的:

public class SomeClass : ICloneable
{
    // Some Code

    // Implementing interface method
    public object Clone()
    {
        // Some Clonning Code
    }
}

现在我的问题是为什么不能使用“SomeClass(因为它是从对象派生的)”作为Clone()方法的返回类型,如果我们考虑Funda的协方差和逆变

有人可以解释一下这个实施Microsoft ????的背后原因

6 个答案:

答案 0 :(得分:8)

让我重新解释一下这个问题:

  

C ++等语言允许覆盖方法具有比重写方法更具体的返回类型。例如,如果我们有类型

abstract class Enclosure {}
class Aquarium : Enclosure {}
abstract class Animal 
{
    public virtual Enclosure GetEnclosure();
}
  

那么这在C#中是不合法的,但是等效代码在C ++中是合法的:

class Fish : Animal
{
    public override Aquarium GetEnclosure() { ... 
  

C ++的这个特性叫做什么?

该功能称为“返回类型协方差”。 (正如另一个答案指出的那样,也可以支持“形式参数类型逆转”,尽管C ++没有。)

  

为什么C#不支持它?

正如我多次指出的那样,我们不必提供不支持功能的原因;所有功能的默认状态为“不支持”。只有当大量的时间和精力投入到实现功能得到支持时。相反, 实现的功能必须有它们的理由,并且考虑到制作它们需要多少成本,这是很好的理由。

也就是说,这个功能有两个很大的“反对点”,这是防止它完成的主要因素。

  1. CLR不支持它。为了完成这项工作,我们基本上必须实现完全匹配的方法,然后创建一个调用它的辅助方法。这是可行的,但它会变得混乱。

  2. 安德斯认为这不是一个很好的语言功能。安德斯是首席架构师,如果他认为这是一个糟糕的特征,那么很有可能它不会完成。 (现在,请注意,我们认为命名和可选参数也不值得花费,但最终确实完成了。有时很明显你必须咬紧牙关并实现一个你并不喜欢的功能美学,以满足现实世界的需求。)

  3. 简而言之,有时会有用,这是一个经常被要求的功能。但是,我们不太可能这样做。该功能的好处不支付其费用;它使方法的语义分析变得相当复杂,我们没有简单的方法来实现它。

答案 1 :(得分:5)

接口实现方差的未破坏实现必须在返回类型中具有协变性,并且在参数类型中具有逆变性。

例如:

public interface IFoo
{
    object Flurp(Array array);
}

public class GoodFoo : IFoo
{
    public int Flurp(Array array) { ... }
}

public class NiceFoo : IFoo
{
    public object Flurp(IEnumerable enumerable) { ... }
}

根据“新”规则,两者都是合法的,对吧?但是这个怎么样:

public class QuestionableFoo : IFoo
{
    public double Flurp(Array array) { ... }
    public object Flurp(IEnumerable enumerable) { ... }
}

有点难以分辨哪个隐式实现更好。第一个是参数类型的精确匹配,但不是返回类型。第二个是返回类型的精确匹配,但不是参数类型。我倾向于第一个,因为使用IFoo界面的人只能给它一个Array,但它仍然不完全清楚。

到目前为止,这并不是最糟糕的。如果我们这样做会怎么样:

public class EvilFoo : IFoo
{
    public object Flurp(ICollection collection) { ... }
    public object Flurp(ICloneable cloneable) { ... }
}

哪一个获奖?这是一个完全有效的重载,但ICollectionICloneable彼此无关,Array实现了它们。我在这里看不到明显的解决方案。

如果我们开始向接口本身添加重载,情况会变得更糟:

public interface ISuck
{
    Stream Munge(ArrayList list);
    Stream Munge(Hashtable ht);
    string Munge(NameValueCollection nvc);
    object Munge(IEnumerable enumerable);
}

public class HateHateHate : ISuck
{
    public FileStream Munge(ICollection collection);
    public NetworkStream Munge(IEnumerable enumerable);
    public MemoryStream Munge(Hashtable ht);
    public Stream Munge(ICloneable cloneable);
    public object Munge(object o);
    public Stream Munge(IDictionary dic);
}

祝你好运,试图解开这个谜团而不会疯狂。

当然,如果断言接口实现应该只支持返回类型方差而不支持参数类型方差,那么所有这些都没有实际意义。但几乎每个人都会认为这样的半实现完全被破坏并开始发布垃圾邮件报告,所以我认为C#团队不会这么做。

我不知道这是否是它今天在C#中不受支持的官方原因,但它应该作为它可能导致的“只写”代码的一个很好的例子,并且它的一部分C#团队的设计理念是试图阻止开发人员编写糟糕的代码。

答案 2 :(得分:2)

您必须完全按照界面中的方式实现接口的方法。 ICloneable的Clone方法返回一个对象,因此SomeClass也必须返回一个对象。但是,您可以在SomeClass的Clone方法中返回SomeClass实例而没有任何问题,但方法定义必须与接口匹配:

public class SomeClass: IClonable
 {
     // Some Code

     //Implementing interface method
     Public object Clone()
      {
        SomeClass ret = new SomeClass();
        // copy date from this instance to ret
        return ret;
      }
 }

答案 3 :(得分:1)

在解释C#决策背后的原因方面,微软的Eric Lippert写了很多关于C#中Contra / CoVariance的解释......这是他博客上的标签列表: http://blogs.msdn.com/ericlippert/archive/tags/Covariance+and+Contravariance/default.aspx

[编辑] 具体到您的问题,这可能是正确的帖子.. http://blogs.msdn.com/ericlippert/archive/2007/10/26/covariance-and-contravariance-in-c-part-five-interface-variance.aspx

答案 4 :(得分:0)

根据C#规范,在覆盖或实现接口方法时,必须使用具有相同签名的方法。请记住,Microsoft不拥有C#。他们的C#编译器只是它们的实现。那么为什么规范会以这种方式做事呢?我只能猜测,但我怀疑这是为了便于实施。

答案 5 :(得分:0)

它看起来像是他们可以使用泛型的东西,但似乎有充分的理由说明他们没有。

这里有人谈到:

http://bytes.com/topic/c-sharp/answers/469671-generic-icloneable

基本上,通用接口允许: public class MyClass : IClonable<MyClass>

还允许: public class MyClass : IClonable<MyOtherClass>

并没有真正带来任何好处,可能会让事情变得混乱。