7

用例是这样的:

public class SomeClass : ICloneable
{
    // Some Code

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

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

有人可以解释一下微软这个实现背后的原因吗????

4

6 回答 6

9

让我重新表述一下这个问题:

诸如 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. Anders 认为这不是一个很好的语言特性。Anders 是首席架构师,如果他认为这是一个糟糕的功能,那么很有可能它不会完成。(现在,请注意,我们认为命名参数和可选参数也不值得付出代价,但最终还是做到了。有时很明显,您确实必须咬紧牙关并实现您并不真正喜欢的功能的美学,以满足现实世界的需求。)

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

于 2010-03-26T00:01:08.370 回答
6

接口实现方差的完整实现必须在返回类型中是协变的,在参数类型中是逆变的

例如:

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) { ... }
}

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

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

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# 团队设计的一部分哲学是试图阻止开发人员编写糟糕的代码。

于 2010-03-23T19:33:44.670 回答
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;
      }
 }
于 2010-03-23T17:49:48.000 回答
1

在解释 C# 决策背后的原因方面,来自 Microsoft 的 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-接口-variance.aspx

于 2010-03-23T17:51:05.853 回答
0

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

于 2010-03-23T17:50:59.213 回答
0

这看起来像是他们可以使用泛型来做的事情,但似乎有充分的理由不这样做。

这里谈到:

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

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

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

这并没有真正提供任何好处,并且可能会使事情变得混乱。

于 2010-03-23T17:53:20.000 回答