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

本文关键字:方差 返回类型 子类 | 更新日期: 2023-09-27 18:24:09

有人知道为什么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
}

感谢您的回复。

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

更新:这个答案写在2011年。在人们提出C#的返回型协方差二十年后,它看起来终于会实现了;我很惊讶。请参阅的底部https://devblogs.microsoft.com/dotnet/welcome-to-c-9-0/用于公告;我相信细节会随之而来。


首先,返回类型对冲没有任何意义;我想你说的是返回类型协方差

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

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

您想知道为什么没有实现该功能。phoog是正确的;该功能没有实现,因为这里从来没有人实现过它。一个必要但不充分的要求是该功能的好处超过了它的成本。

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

好处很小。

高成本、小效益的功能和简单的解决方法很快就会被淘汰。我们有更高的优先事项。

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

这是一个简短的答案,这里有一个稍长的答案。。。

什么是差异

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

  • 如果转换的结果是一个保持原始类型层次结构的"方向"的类型层次结构,则转换为co变体
  • 如果转换的结果是反转原始"方向"的类型层次结构,则转换为反向变体
  • 如果转换的结果是一堆不相关的类型,则转换是-变体中的

C#中的方差是多少

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

  • 如果I<Parent>>I<Child>,则T是协变的(保持ParentChild之间的原始"方向")
  • 如果CCD_ 13<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#设计人员决定做正确的事情,在编译时完全解决问题,并避免任何运行时检查的需要(与数组不同)。

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

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#不支持它的问题。。。我之所以研究这个问题,是因为我想有一个接口,在这个接口中,我可以返回实现特定接口的任意类的集合/枚举值。

如果您能够很好地定义要返回的具体类型,那么只需相应地定义接口即可。然后,它将在编译时检查是否满足约束(任何类型的子类型)。

我举了一个例子,可能会对你有所帮助。

正如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