关于子类返回类型的C#协方差

时间:2012-02-10 22:45:29

标签: c# .net inheritance covariance

有谁知道为什么C#不支持协变返回类型?即使在尝试使用接口时,编译器也会抱怨它是不允许的。请参阅以下示例。

class Order
{
    private Guid? _id;
    private String _productName;
    private double _price;

    protected Order(Guid? id, String productName, double price)
    {
        _id = id;
        _productName = productName;
        _price = price;
    }

    protected class Builder : IBuilder<Order>
    {
        public Guid? Id { get; set; }
        public String ProductName { get; set; }
        public double Price { get; set; }

        public virtual Order Build()
        {
            if(Id == null || ProductName == null || Price == null)
                throw new InvalidOperationException("Missing required data!");

            return new Order(Id, ProductName, Price);
        }
    }            
}

class PastryOrder : Order
{
    PastryOrder(Guid? id, String productName, double price, PastryType pastryType) : base(id, productName, price)
    {

    }

    class PastryBuilder : Builder
    {
        public PastryType PastryType {get; set;}

        public override PastryOrder Build()
        {
            if(PastryType == null) throw new InvalidOperationException("Missing data!");
            return new PastryOrder(Id, ProductName, Price, PastryType);
        }
    }
}

interface IBuilder<in T>
{
    T Build();
}

public enum PastryType
{
    Cake,
    Donut,
    Cookie
}

感谢您的回复。

4 个答案:

答案 0 :(得分:25)

首先,返回类型逆转没有任何意义;我想你在谈论返回类型协方差

有关详细信息,请参阅此问题:

Does C# support return type covariance?

您想知道为什么没有实现该功能。 phoog是正确的;该功能未实现,因为此处没有人实现它。必要但不充分的要求是该功能的好处超过其成本。

成本相当可观。运行时本身不支持该功能,它直接影响我们使C#版本化的目标,因为它引入了另一种形式的脆弱基类问题,Anders认为它不是一个有趣或有用的功能,如果你真的想要它,你可以通过编写小帮助方法使它工作。 (这正是C ++的CIL版本所做的。)

好处很小。

高成本,小型优势功能以及简单的解决方法可以非常快速地将其分类。我们有更高的优先级。

答案 1 :(得分:9)

不能输出逆变通用参数,因为在编译时不能保证安全,C#设计者决定不将必要的检查延长到运行时间。

这是简短的答案,这里稍长一点......

什么是差异?

Variance是应用于类型层次结构的转换的属性:

  • 如果转换的结果是保持原始类型层次结构的“方向”的类型层次结构,则转换为 co <​​/ strong> -variant。
  • 如果转换的结果是反转原始“方向”的类型层次结构,则转换为 contra -variant。
  • 如果转换结果是一堆不相关的类型,则转换为 -variant。

C#中的差异是什么?

在C#中,“转换”是“用作通用参数”。例如,假设类Parent由类Child继承。我们将这个事实表示为:Parent&gt; Child(因为所有Child个实例也是Parent个实例,但不一定相反,因此Parent是“更大”)。我们还说我们有一个通用接口I<T>

  • 如果I<Parent>&gt; I<Child>,T是协变的(保留ParentChild之间的原始“方向”。
  • 如果I<Parent>&lt; I<Child>,T是逆变的(原来的“方向”是相反的)。
  • 如果I<Parent>I<Child>无关,则T不变。

那么,什么可能不安全?

如果C#编译器实际同意编译以下代码......

class Parent {
}

class Child : Parent {
}

interface I<in T> {
    T Get(); // Imagine this actually compiles.
}

class G<T> : I<T> where T : new() {
    public T Get() {
        return new T();
    }
}

// ...

I<Child> g = new G<Parent>(); // OK since T is declared as contravariant, thus "reversing" the type hierarchy, as explained above.
Child child = g.Get(); // Yuck!

...这会导致运行时出现问题:实例化Parent并将其分配给对Child的引用。由于Parent不是Child,这是错误的!

最后一行在编译时看起来正常,因为I<Child>.Get被声明为返回Child,但我们无法在运行时完全“信任”它。 C#设计者决定做正确的事情并在编译时完全解决问题,并避免任何运行时检查的需要(与数组不同)。

(出于类似但“反向”的原因,协变通用参数不能用作输入。)

答案 2 :(得分:7)

Eric Lippert在此网站上撰写了一些关于方法覆盖的返回方法协方差的帖子,但我没有看到为什么该功能不受支持。但他提到,没有计划支持它:https://stackoverflow.com/a/4349584/385844

Eric还喜欢说“为什么不支持 X ”的答案总是一样的:因为没有人设计,实现和测试(等等) X 即可。这方面的一个例子是:https://stackoverflow.com/a/1995706/385844

缺乏此功能可能有一些哲学原因;也许埃里克会看到这个问题并启发我们。

修改

Pratik在评论中指出:

interface IBuilder<in T> 
{ 
    T Build(); 
} 

应该是

interface IBuilder<out T> 
{ 
    T Build(); 
} 

这将允许您实施PastryOrder : IBuilder<PastryOrder>,然后您可以

IBuilder<Order> builder = new PastryOrder();

您可以使用两种或三种方法来解决问题,但是,正如您所指出的,返回方法协方差不是这些方法之一,并且这些信息都没有回答为什么C#不支持它的问题

答案 3 :(得分:0)

只是发布这个地方谷歌找到它... 我正在调查这个因为我想要一个接口,我可以在其中返回实现特定接口的任意类的集合/枚举。

如果您可以定义要返回的具体类型,则可以相应地定义您的界面。然后它将在编译时检查是否满足约束(任何子类型)。

我提供了一个可能对您有帮助的例子。

正如Branko Dimitrijevic指出的那样,通常允许协变返回类型通常是不安全的。但使用它,它是类型安全的,你甚至可以嵌套(例如interface A<T, U> where T: B<U> where U : C

(免责声明:我昨天开始使用C#,所以对于最佳实践我可能完全错误,有经验的人应该对此发表评论:))


示例:

使用

interface IProvider<T, Coll> where T : ProvidedData where Coll : IEnumerable<T>
{
  Coll GetData();
}

class XProvider : IProvider<X, List<X>>
{
  List<X> GetData() { ... }
}

调用

new XProvider().GetData

有效且在这种情况下是安全的。在这种情况下,您只需要定义要返回的类型。


更多相关信息:http://msdn.microsoft.com/de-de/library/d5x73970.aspx