Categories
程式開發

C#的未來:協變返回類型


一個常見的API設計問題是無法在重寫方法時使用更具體的返回類型。 Clone方法就是一個很好的例子。

public abstract Request Clone();

在子類中,你可能會希望像下面這樣實現這個方法:

public override FtpRequest Clone() { ... }

由於FtpRequest是Request的子類,從邏輯上講這是合理的。但在.NET中你不能這樣實現,因為重寫必須精確匹配。你也不能通過重寫獲得一個僅有返回類型不同的新方法。所以通常你會得到一些複雜的東西,比如:

public Request Clone() => OnClone();
protected abstract Request OnClone();

然後,在子類裡:

public new FtpRequest Clone() => (FtpRequest)OnClone();
protected override Request OnClone() { ... }

提案49在“協變返回類型”中探討了改變被重寫方法返回類型的能力。
在2017年最初提出時,這個特性應該是使用一些“編譯器魔法”實現的。到2019年10月,重點已經轉向使它成為CLR的一等特性。

協變返回類型規範草案中,IL指令.override將變成:

重寫方法的返回類型必須可以通過標識或隱式引用轉換為被重寫的基方法的返回類型。

當前的規則是:

重寫方法和被重寫的基方法應具有相同的返回類型。

屬性和索引器

屬性和索引器包括在此特性中,但僅當它們是只讀的。該特性不會對逆變屬性和索引設置器提供匹配支持。

接口

接口上的方法可以使用與子類/基類相同的規則重寫基接口上的協變方法。

當類實現接口時,實現方法可以與接口方法協變。

為了進行接口映射,類成員A在以下情況下與接口成員B匹配:

A和B是方法,A和B的名稱和形式參數列表相同,A的返回類型可以通過一個隱式引用標識轉換為B的返回類型。

對於隱式實現的接口,這種規則變化可能會導致破壞性更改。這種情況會在子類重新實現基類已經實現的接口時發生。

interface I1 { object M(); }
class C1 : I1 { public object M() { return "C1.M"; } }
class C2 : C1, I1 { public new string M() { return "C2.M"; } }

為了避免破壞性更改,Andy Gocke對規則做了一個小小的修改:

如果沒有其他實現(包括默認實現),我們是否可以更改映射成員的搜索,將具有不同協變返回類型的隱式實現考慮進來?

遺憾的是,這與接口的默認實現不兼容。 Neal Gafter寫到:

我看不出這在二進制兼容的情況下是如何工作的。如果發布了帶有默認實現的新版本的接口,那麼運行時將更改為使用該接口,而不是使用基類的實現?

微軟內部正在跟踪對協變返回類型提供必要的運行時支持的優先級。

原文鏈接:

C# Futures: Covariant Return Types