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

MgS*_*Sam 7 .net c# 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
}
Run Code Online (Sandbox Code Playgroud)

感谢您的回复.

Eri*_*ert 25

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

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

C#是否支持返回类型协方差?

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

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

好处很小.

高成本,小巧的功能以及简单的解决方法可以非常快速地进行分类.我们有更高的优先事项.

  • @MgSam:不客气.是的,async/await是*far*更复杂,这正是我们将精力集中在它上面的原因.我们有机会在C#中使异步编程*从根本上变得更容易*.这是一种方式,比制作一个使它看起来像一个新的虚方法的语法糖实际上覆盖一个具有不同签名的方法更重要. (3认同)
  • 谢谢你的回复埃里克.一个显而易见的问题是,您在链接到的主题中提供了解决方案,似乎编译器可以像C++ CIL编译器那样进行重写.事实上,编译器为其他功能(例如async/await)所做的重写类型似乎比这样的功能要复杂得多.我也不同意这些好处很小; 正如您所提到的,其他语言支持该功能,除了我自己以外的其他开发人员已经询问过它. (2认同)

Bra*_*vic 9

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

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

差异是什么?

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

  • 如果转换的结果是保持原始类型层次结构的"方向"的类型层次结构,则转换是变的.
  • 如果转换的结果是反转原始"方向" 的类型层次结构,则转换是变量.
  • 如果转换的结果是一堆不相关的类型,改造 -variant.

什么是C#的差异?

在C#中,"转换"是"用作通用参数".例如,假设一个类Parent是由类继承的Child.让我们将这个事实表示为:Parent> Child(因为所有Child实例都是Parent实例,但不一定是反过来,因此Parent"更大").我们还说我们有一个通用接口I<T>:

  • 如果I<Parent>> I<Child>,则T是协变的(原始"方向"在Parent和之间Child).
  • 如果I<Parent>< 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!
Run Code Online (Sandbox Code Playgroud)

...这会在运行时导致问题:a Parent被实例化并分配给引用Child.既然Parent不是Child,这是错的!

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

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


pho*_*oog 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(); 
} 
Run Code Online (Sandbox Code Playgroud)

应该

interface IBuilder<out T> 
{ 
    T Build(); 
} 
Run Code Online (Sandbox Code Playgroud)

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

IBuilder<Order> builder = new PastryOrder();
Run Code Online (Sandbox Code Playgroud)

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