简体   繁体   English

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

[英]C# Covariance on subclass return types

Does anyone know why covariant return types are not supported in C#?有谁知道为什么 C# 不支持协变返回类型? Even when attempting to use an interface, the compiler complains that it is not allowed.即使在尝试使用接口时,编译器也会抱怨它是不允许的。 See the following example.请参阅以下示例。

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
}

Thanks for any responses.感谢您的任何回复。

UPDATE: This answer was written in 2011. After two decades of people proposing return type covariance for C#, it looks like it will finally be implemented;更新:这个答案是在 2011 年写的。经过二十年的人们为 C# 提出返回类型协方差,看起来它最终会被实现; I am rather surprised.我比较惊讶。 See the bottom of https://devblogs.microsoft.com/dotnet/welcome-to-c-9-0/ for the announcement;公告见https://devblogs.microsoft.com/dotnet/welcome-to-c-9-0/底部; I'm sure details will follow.我相信细节会随之而来。


First off, return type contravariance doesn't make any sense;首先,返回类型逆变没有任何意义; I think you are talking about return type covariance .我想你在谈论返回类型协方差

See this question for details:有关详细信息,请参阅此问题:

Does C# support return type covariance? C# 是否支持返回类型协方差?

You want to know why the feature is not implemented.您想知道为什么未实现该功能。 phoog is correct; phoog 是正确的; the feature is not implemented because no one here ever implemented it.该功能没有实现,因为这里没有人实现过它。 A necessary but insufficient requirement is that the feature's benefits exceed its costs.一个必要但不充分的要求是该功能的收益超过其成本。

The costs are considerable.费用相当可观。 The feature is not supported natively by the runtime, it works directly against our goal to make C# versionable because it introduces yet another form of the brittle base class problem, Anders doesn't think it is an interesting or useful feature, and if you really want it, you can make it work by writing little helper methods.运行时本身不支持该功能,它直接违背了我们使 C# 可版本化的目标,因为它引入了另一种形式的脆弱基类问题,Anders 认为它​​不是一个有趣或有用的功能,如果你真的想要它,您可以通过编写小助手方法使其工作。 (Which is exactly what the CIL version of C++ does.) (这正是 C++ 的 CIL 版本所做的。)

The benefits are small.好处很小。

High cost, small benefit features with an easy workaround get triaged away very quickly .具有简单解决方法的高成本、小收益功能很快就会被分类 We have far higher priorities.我们有更高的优先级。

The contravariant generic parameter cannot be output, because that cannot be guaranteed to be safe at compile time, and C# designers made a decision not to prolong the necessary checks to the run-time.逆变泛型参数不能输出,因为不能保证在编译时是安全的,C# 设计者决定不将必要的检查延长到运行时。

This is the short answer, and here is a slightly longer one...这是简短的答案,这里有一个稍长的答案......

What is variance?什么是方差?

Variance is a property of a transformation applied to a type hierarchy:方差是应用于类型层次结构的转换属性

  • If the result of the transformation is a type hierarchy that keeps the "direction" of the original type hierarchy, the transformation is co -variant.如果转换的结果是保持原始类型层次结构“方向”的类型层次结构,则转换是变的。
  • If the result of the transformation is a type hierarchy that reverses the original "direction", the transformation is contra -variant.如果变换的结果是一个类型层次结构逆转原来的“方向”,变换是禁忌-variant。
  • If the result of the transformation is a bunch of unrelated types, the transformation is in -variant.如果转换的结果是一堆不相关的类型,则转换是in- variant。

What is variance in C#? C# 中的方差是什么?

In C#, the "transformation" is "being used as a generic parameter".在 C# 中,“转换”是“用作泛型参数”。 For example, let's say a class Parent is inherited by class Child .例如,假设类Parent由类Child继承。 Let's denote that fact as: Parent > Child (because all Child instances are also Parent instances, but not necessarily the other way around, hence Parent is "bigger").让我们将该事实表示为: Parent > Child (因为所有Child实例也是Parent实例,但不一定相反,因此Parent是“更大”的)。 Let's also say we have a generic interface I<T> :假设我们有一个通用接口I<T>

  • If I<Parent> > I<Child> , the T is covariant (the original "direction" between Parent and Child is kept).如果I<Parent> > I<Child> ,则 T 是协变的(保留ParentChild之间的原始“方向”)。
  • If I<Parent> < I<Child> , the T is contravariant (the original "direction" is reversed).如果I<Parent> < I<Child> ,则 T 是逆变的(原始“方向”相反)。
  • If I<Parent> is unrelated to I<Child> , the T is invariant.如果I<Parent>I<Child>无关,则 T 是不变的。

So, what is potentially unsafe?那么,什么是潜在的不安全?

If C# compiler actually agreed to compile the following code...如果 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!

...this would lead to a problem at run-time: a Parent is instantiated and assigned to a reference to Child . ...这将导致运行时出现问题: Parent被实例化并分配给对Child的引用。 Since Parent is not Child , this is wrong!由于Parent不是Child ,这是错误的!

The last line looks OK at compile-time since I<Child>.Get is declared to return Child , yet we could not fully "trust" it at run-time.最后一行在编译时看起来没问题,因为I<Child>.Get被声明为返回Child ,但我们不能在运行时完全“信任”它。 C# designers decided to do the right thing and catch the problem completely at compile-time, and avoid any need for the run-time checks (unlike for arrays). C# 设计者决定做正确的事情并在编译时完全捕获问题,并避免对运行时检查的任何需要(与数组不同)。

(For similar but "reverse" reasons, covariant generic parameter cannot be used as input.) (出于类似但“相反”的原因,协变通用参数不能用作输入。)

Eric Lippert has written a few posts on this site about return method covariance on method overrides, without as far as I can see addressing why the feature is unsupported. Eric Lippert 在此站点上写了一些关于方法覆盖的返回方法协方差的帖子,但据我所知,没有说明为什么该功能不受支持。 He has mentioned, though, that there are no plans to support it: https://stackoverflow.com/a/4349584/385844不过,他提到没有计划支持它: https : //stackoverflow.com/a/4349584/385844

Eric is also fond of saying that the answer to "why isn't X supported" is always the same: because nobody has designed, implemented, and tested (etc.) X . Eric 还喜欢说“为什么不支持X ”的答案总是一样的:因为没有人设计、实现和测试(等) X An example of that is here: https://stackoverflow.com/a/1995706/385844一个例子在这里: https : //stackoverflow.com/a/1995706/385844

There may be some philosophical reason for the lack of this feature;缺少此功能可能有一些哲学原因; perhaps Eric will see this question and enlighten us.也许埃里克会看到这个问题并启发我们。

EDIT编辑

As Pratik pointed out in a comment:正如普拉蒂克在评论中指出的那样:

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

should be应该

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

That would allow you to implement PastryOrder : IBuilder<PastryOrder> , and you could then have这将允许您实现PastryOrder : IBuilder<PastryOrder> ,然后您就可以拥有

IBuilder<Order> builder = new PastryOrder();

There are probably two or three approaches you could use to solve your problem, but, as you note, return method covariance is not one of those approaches, and none of this information answers the question of why C# doesn't support it.可能有两种或三种方法可以用来解决您的问题,但是,正如您所注意到的,返回方法协方差不是其中一种方法,并且这些信息都没有回答为什么 C# 不支持它的问题。

Just to post this somewhere google finds it... I was looking into this because I wanted to have an interface in which I can return collections / enumerables of arbitrary classes implementing a specific interface.只是为了将它发布到谷歌找到的地方......我正在研究这个,因为我想要一个接口,我可以在其中返回实现特定接口的任意类的集合/枚举。

If you're fine with defining the concrete types you want to return, you can simply define your interface accordingly.如果您可以定义要返回的具体类型,则可以简单地相应地定义您的接口。 It will then check at compile time that the constraints (subtype of whatever) are met.然后它将在编译时检查是否满足约束(任何子类型)。

I provided an example, that might help you.我提供了一个例子,可能对你有帮助。

As Branko Dimitrijevic pointed out, usually it is unsafe to allow covariant return types in general.正如 Branko Dimitrijevic 指出的那样,通常允许协变返回类型通常是不安全的。 But using this, it's type-safe and you can even nest this (eg interface A<T, U> where T: B<U> where U : C )但是使用它,它是类型安全的,你甚至可以嵌套它(例如interface A<T, U> where T: B<U> where U : C

(Disclaimer: I started using C# yesterday, so I might be completely wrong regarding best practices, someone with more experience should please comment on this :) ) (免责声明:我昨天开始使用 C#,所以我在最佳实践方面可能完全错误,请有更多经验的人对此发表评论:))


Example:例子:

Using使用

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

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

calling打电话

new XProvider().GetData

works and in this case is safe.有效,在这种情况下是安全的。 You only have to define the types you want to return in this case.在这种情况下,您只需定义要返回的类型。


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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM