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

发布于 2025-01-05 05:12:44 字数 1412 浏览 1 评论 0原文

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

感谢您的任何回复。

Does anyone know why covariant return types are not supported in 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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

小霸王臭丫头 2025-01-12 05:12:44

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


首先,返回类型逆变没有任何意义;我认为您正在谈论返回类型协方差

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

C# 支持返回类型协方差吗?< /a>

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

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

好处很小。

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

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; I am rather surprised. See the bottom of https://devblogs.microsoft.com/dotnet/welcome-to-c-9-0/ for the announcement; 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?

You want to know why the feature is not implemented. phoog is correct; 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. (Which is exactly what the CIL version of C++ does.)

The benefits are small.

High cost, small benefit features with an easy workaround get triaged away very quickly. We have far higher priorities.

姐不稀罕 2025-01-12 05:12:44

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

这是简短的答案,这是一个稍长的答案……

什么是方差?

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

  • 如果转换的结果是保持原始类型层次结构“方向”的类型层次结构,则变换是协变的。
  • 如果转换的结果是一个与原始“方向”相反的类型层次结构,则该转换是变体。
  • 如果转换的结果是一堆不相关的类型,则转换是变体。

C# 中什么是方差?

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

  • 如果 I > I,T 是协变的(保留 ParentChild 之间的原始“方向”)。
  • 如果I < I,T 是逆变的(原始“方向”相反)。
  • 如果II无关,则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 并将其分配给对 ChildChild 的引用代码>.由于 Parent 不是 Child,这是错误的!

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

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

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.

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.
  • If the result of the transformation is a bunch of unrelated types, the transformation is in-variant.

What is variance in C#?

In C#, the "transformation" is "being used as a generic parameter". For example, let's say a class Parent is inherited by class 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"). Let's also say we have a generic interface I<T>:

  • If I<Parent> > I<Child>, the T is covariant (the original "direction" between Parent and Child is kept).
  • If I<Parent> < I<Child>, the T is contravariant (the original "direction" is reversed).
  • If I<Parent> is unrelated to I<Child>, the T is invariant.

So, what is potentially unsafe?

If C# compiler actually agreed to compile the following code...

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. Since Parent is not Child, this is wrong!

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. 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).

(For similar but "reverse" reasons, covariant generic parameter cannot be used as input.)

违心° 2025-01-12 05:12:44

Eric Lippert 在此网站上写了几篇关于方法重写的返回方法协方差的文章,但据我所知,没有解决该功能不受支持的原因。不过,他提到没有计划支持它:https://stackoverflow.com/a/4349584/385844

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

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

编辑

正如Pratik在评论中指出的:

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

应该是

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

这将允许您实现PastryOrder:IBuilder,然后您可以有

IBuilder<Order> builder = new PastryOrder();

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

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. He has mentioned, though, that there are no plans to support it: 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. An example of that is here: 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

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.

℡Ms空城旧梦 2025-01-12 05:12:44

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

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

我提供了一个例子,可能对你有帮助。

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

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. But using this, it's type-safe and you can even nest this (e. g. 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 :) )


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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文