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
}
感谢您的回复。
更新:这个答案写在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是协变的(保持Parent
和Child
之间的原始"方向") - 如果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