簡體   English   中英

子類返回類型的 C# 協方差

[英]C# Covariance on subclass return types

有誰知道為什么 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# 設計者決定不將必要的檢查延長到運行時。

這是簡短的答案,這里有一個稍長的答案......

什么是方差?

方差是應用於類型層次結構的轉換屬性

  • 如果轉換的結果是保持原始類型層次結構“方向”的類型層次結構,則轉換是變的。
  • 如果變換的結果是一個類型層次結構逆轉原來的“方向”,變換是禁忌-variant。
  • 如果轉換的結果是一堆不相關的類型,則轉換是in- variant。

C# 中的方差是什么?

在 C# 中,“轉換”是“用作泛型參數”。 例如,假設類Parent由類Child繼承。 讓我們將該事實表示為: Parent > Child (因為所有Child實例也是Parent實例,但不一定相反,因此Parent是“更大”的)。 假設我們有一個通用接口I<T>

  • 如果I<Parent> > I<Child> ,則 T 是協變的(保留ParentChild之間的原始“方向”)。
  • 如果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!

...這將導致運行時出現問題: 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

缺少此功能可能有一些哲學原因; 也許埃里克會看到這個問題並啟發我們。

編輯

正如普拉蒂克在評論中指出的那樣:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM