協變數傳回Covariant returns

總結Summary

支援 變數傳回型別。Support covariant return types. 具體而言,允許覆寫方法來宣告比它所覆寫的方法更衍生的傳回型別,類似于允許覆寫唯讀屬性來宣告更衍生的型別。Specifically, permit the override of a method to declare a more derived return type than the method it overrides, and similarly to permit the override of a read-only property to declare a more derived type. 在更多衍生型別中出現覆寫宣告的要求,必須至少提供傳回型別,如同在基底類型中的覆寫中所出現的一樣。Override declarations appearing in more derived types would be required to provide a return type at least as specific as that appearing in overrides in its base types. 方法或屬性的呼叫端會靜態接收來自調用的更精簡傳回型別。Callers of the method or property would statically receive the more refined return type from an invocation.

動機Motivation

這是程式碼中常見的模式,不同的方法名稱必須在程式碼中使用,才能解決覆寫必須傳回與覆寫方法相同類型的語言條件約束。It is a common pattern in code that different method names have to be invented to work around the language constraint that overrides must return the same type as the overridden method.

這在 factory 模式中很有用。This would be useful in the factory pattern. 例如,在 Roslyn 程式碼基底中,我們會For example, in the Roslyn code base we would have

class Compilation ...
{
    public virtual Compilation WithOptions(Options options)...
}
class CSharpCompilation : Compilation
{
    public override CSharpCompilation WithOptions(Options options)...
}

詳細設計Detailed design

這是 c # 中的協變數傳回 類型 規格。This is a specification for covariant return types in C#. 我們的目的是允許覆寫方法以傳回比它所覆寫的方法更衍生的傳回型別,類似于允許覆寫唯讀屬性以傳回更多衍生的傳回型別。Our intent is to permit the override of a method to return a more derived return type than the method it overrides, and similarly to permit the override of a read-only property to return a more derived return type. 方法或屬性的呼叫端會靜態地從調用接收更精簡的傳回型別,而出現在更多衍生型別的覆寫則需要至少提供傳回型別,就像在基底型別中所出現的覆寫一樣。Callers of the method or property would statically receive the more refined return type from an invocation, and overrides appearing in more derived types would be required to provide a return type at least as specific as that appearing in overrides in its base types.


類別方法覆寫Class Method Override

類別覆寫方法上的現有條件約束The existing constraint on class override methods

  • 覆寫方法和覆寫的基底方法具有相同的傳回類型。The override method and the overridden base method have the same return type.

修改為is modified to

  • 覆寫方法必須具有可透過身分識別轉換轉換的傳回型別,或 (如果方法的傳回型別不 是參考傳回,) 隱含 參考轉換為覆寫基底方法的傳回型別。The override method must have a return type that is convertible by an identity conversion or (if the method has a value return - not a ref return) implicit reference conversion to the return type of the overridden base method.

而且下列其他需求會附加至該清單:And the following additional requirements are appended to that list:

  • 覆寫方法必須具有可透過身分識別轉換來轉換的傳回型別,或 (如果方法的值傳回-不是 ref 傳回) 隱含參考轉換,表示覆寫方法的 (直接或間接) 基底類型中宣告之覆寫基底方法的傳回型別。The override method must have a return type that is convertible by an identity conversion or (if the method has a value return - not a ref return) implicit reference conversion to the return type of every override of the overridden base method that is declared in a (direct or indirect) base type of the override method.
  • 覆寫方法的傳回型別至少必須可以像覆寫方法一樣存取, (存取範圍網域) 。The override method's return type must be at least as accessible as the override method (Accessibility domains).

此條件約束允許類別中的覆寫方法 private 具有傳回 private 型別。This constraint permits an override method in a private class to have a private return type. 不過,它需要 public 類型中的覆寫方法 public 具有傳回 public 型別。However it requires a public override method in a public type to have a public return type.

類別屬性和索引子覆寫Class Property and Indexer Override

類別覆寫屬性的現有條件約束The existing constraint on class override properties

覆寫屬性宣告必須指定與繼承屬性完全相同的存取範圍修飾詞和名稱,而且覆 寫的類型和繼承的屬性之間必須有身分識別轉換。An overriding property declaration shall specify the exact same accessibility modifiers and name as the inherited property, and there shall be an identity conversion between the type of the overriding and the inherited property. 如果繼承的屬性只有一個存取子 (也就是,如果繼承的屬性是唯讀的或僅限寫入的) ,則覆寫的屬性應該只包含該存取子。If the inherited property has only a single accessor (i.e., if the inherited property is read-only or write-only), the overriding property shall include only that accessor. 如果繼承的屬性同時包含這兩個存取子 (亦即,如果繼承的屬性為讀寫) ,則覆寫屬性可以包含單一存取子或兩個存取子。If the inherited property includes both accessors (i.e., if the inherited property is read-write), the overriding property can include either a single accessor or both accessors.

修改為is modified to

覆寫屬性宣告必須指定與繼承的屬性完全相同的存取範圍修飾詞和名稱, (而且如果繼承的屬性是唯讀的,而且有值傳回-不是 ref 傳回) 隱含參考從覆寫屬性的類型轉換為繼承的屬性類型An overriding property declaration shall specify the exact same accessibility modifiers and name as the inherited property, and there shall be an identity conversion or (if the inherited property is read-only and has a value return - not a ref return) implicit reference conversion from the type of the overriding property to the type of the inherited property. 如果繼承的屬性只有一個存取子 (也就是,如果繼承的屬性是唯讀的或僅限寫入的) ,則覆寫的屬性應該只包含該存取子。If the inherited property has only a single accessor (i.e., if the inherited property is read-only or write-only), the overriding property shall include only that accessor. 如果繼承的屬性同時包含這兩個存取子 (亦即,如果繼承的屬性為讀寫) ,則覆寫屬性可以包含單一存取子或兩個存取子。If the inherited property includes both accessors (i.e., if the inherited property is read-write), the overriding property can include either a single accessor or both accessors. 覆寫屬性的類型至少必須可以像覆寫屬性一樣存取 (存取範圍網域) 。The overriding property's type must be at least as accessible as the overriding property (Accessibility domains).


下列草稿規格的其餘部分建議進一步延伸至介面方法的協變數傳回,稍後再考慮。The remainder of the draft specification below proposes a further extension to covariant returns of interface methods to be considered later.

介面方法、屬性和索引子覆寫Interface Method, Property, and Indexer Override

新增至介面中允許的成員類型,並在 c # 8.0 中新增維度功能,我們會進一步新增 override 成員支援以及協變數傳回。Adding to the kinds of members that are permitted in an interface with the addition of the DIM feature in C# 8.0, we further add support for override members along with covariant returns. 這些會遵循 override 針對類別指定的成員規則,但有下列差異:These follow the rules of override members as specified for classes, with the following differences:

類別中的下列文字:The following text in classes:

覆寫宣告所覆寫的方法稱為覆 寫基底方法The method overridden by an override declaration is known as the overridden base method. 針對在類別中宣告的覆寫方法 M C ,會藉由檢查的每個基類來判斷覆寫的基底方法, C 其開頭為的直接基類, C 並繼續每個後續的直接基類,直到指定的基類型別中至少有一個可存取的方法,其簽章與 M 替換型別引數之後的簽章相同。For an override method M declared in a class C, the overridden base method is determined by examining each base class of C, starting with the direct base class of C and continuing with each successive direct base class, until in a given base class type at least one accessible method is located which has the same signature as M after substitution of type arguments.

提供介面的對應規格:is given the corresponding specification for interfaces:

覆寫宣告所覆寫的方法稱為 * 覆 寫的基底方法 _。The method overridden by an override declaration is known as the *overridden base method _. 針對在介面中宣告的覆寫方法 M I ,會藉由檢查的每個直接或間接基底介面來判斷覆寫的基底方法 I ,並收集宣告可存取方法的介面集合,該方法的簽章與 M 替代類型引數的替代之後相同。For an override method M declared in an interface I, the overridden base method is determined by examining each direct or indirect base interface of I, collecting the set of interfaces declaring an accessible method which has the same signature as M after substitution of type arguments. 如果這組介面有 _most 衍生類型 *,則在這個集合中的每個類型都有身分識別或隱含參考轉換,而且該類型包含唯一的這類方法宣告,則為覆 寫的基底方法If this set of interfaces has a _most derived type*, to which there is an identity or implicit reference conversion from every type in this set, and that type contains a unique such method declaration, then that is the overridden base method.

同樣地, override15.7.6 Virtual、sealed、override 和 abstract 存取 子的類別中,也允許在介面中指定屬性和索引子。We similarly permit override properties and indexers in interfaces as specified for classes in 15.7.6 Virtual, sealed, override, and abstract accessors.

名稱查閱Name Lookup

存在類別宣告的名稱查閱, override 目前會藉由從類別階層中最衍生的宣告(從 override 識別碼的辨識符號的類型開始, (或沒有任何限定詞) 來修改所找到的成員詳細資料,來修改名稱查閱的結果 thisName lookup in the presence of class override declarations currently modify the result of name lookup by imposing on the found member details from the most derived override declaration in the class hierarchy starting from the type of the identifier's qualifier (or this when there is no qualifier). 例如,在 12.6.2.2 對應的參數 中,我們有For example, in 12.6.2.2 Corresponding parameters we have

如果是在類別中定義的虛擬方法和索引子,則會從第一次宣告或覆寫函式成員(從接收端靜態型別開始時找到),並搜尋其基類來挑選參數清單。For virtual methods and indexers defined in classes, the parameter list is picked from the first declaration or override of the function member found when starting with the static type of the receiver, and searching through its base classes.

我們新增了to this we add

針對在介面中定義的虛擬方法和索引子,會從宣告或覆寫在最衍生類型中找到的函式成員,在包含函式成員覆寫宣告的類型之間挑選。For virtual methods and indexers defined in interfaces, the parameter list is picked from the declaration or override of the function member found in the most derived type among those types containing the declaration of override of the function member. 如果沒有唯一的這類類型存在,則這是編譯時期錯誤。It is a compile-time error if no unique such type exists.

針對屬性或索引子存取的結果型別,現有的文字For the result type of a property or indexer access, the existing text

  • 如果我識別實例屬性,則結果會是具有相關聯的實例運算式 E 的屬性存取,以及屬於屬性類型的關聯型別。If I identifies an instance property, then the result is a property access with an associated instance expression of E and an associated type that is the type of the property. 如果 T 是類別類型,則會從第一次宣告或覆寫屬性(從 T 開始時找到),並搜尋其基類來挑選相關聯的類型。If T is a class type, the associated type is picked from the first declaration or override of the property found when starting with T, and searching through its base classes.

擴充is augmented with

如果 T 是介面型別,則會從最衍生的 T 或其直接或間接基底介面中找到的屬性宣告或覆寫來挑選相關聯的類型。If T is an interface type, the associated type is picked from the declaration or override of the property found in the most derived of T or its direct or indirect base interfaces. 如果沒有唯一的這類類型存在,則這是編譯時期錯誤。It is a compile-time error if no unique such type exists.

12.7.7.3 索引子存取 中應進行類似的變更A similar change should be made in 12.7.7.3 Indexer access

12.7.6 調用運算式 中,我們會增加現有的文字In 12.7.6 Invocation expressions we augment the existing text

  • 否則,結果會是一個值,其中包含方法或委派的傳回類型相關聯的類型。Otherwise, the result is a value, with an associated type of the return type of the method or delegate. 如果叫用的是實例方法,而接收者是類別類型 T,則會從第一個宣告或覆寫在開頭為 T 並搜尋其基類時所找到的方法來挑選相關聯的類型。If the invocation is of an instance method, and the receiver is of a class type T, the associated type is picked from the first declaration or override of the method found when starting with T and searching through its base classes.

取代為with

如果調用是實例方法,而且接收者是介面型別 T,則會從 T 和其直接和間接基底介面的最高衍生介面中找到的方法宣告或覆寫相關聯的類型。If the invocation is of an instance method, and the receiver is of an interface type T, the associated type is picked from the declaration or override of the method found in the most derived interface from among T and its direct and indirect base interfaces. 如果沒有唯一的這類類型存在,則這是編譯時期錯誤。It is a compile-time error if no unique such type exists.

隱含介面實現Implicit Interface Implementations

這一節的規格This section of the specification

基於介面對應的目的,類別成員會 A 在下列情況下符合介面成員 BFor purposes of interface mapping, a class member A matches an interface member B when:

  • AB 是方法,且和的名稱、類型和型式參數清單 A B 相同。A and B are methods, and the name, type, and formal parameter lists of A and B are identical.
  • AB 是屬性,和的名稱和型 AB 都相同,而且 A 具有相同的存取子,因為 B A 如果它不是明確的介面成員實) ,則允許 (有其他存取子。A and B are properties, the name and type of A and B are identical, and A has the same accessors as B (A is permitted to have additional accessors if it is not an explicit interface member implementation).
  • AB 是事件,而且和的名稱和類型 A B 都相同。A and B are events, and the name and type of A and B are identical.
  • AB 是索引子、和的型別和型式參數清單 A B 相同,而且 A 具有相同的存取子,因為 B A 如果它不是明確的介面成員實) ,則允許 (有其他存取子。A and B are indexers, the type and formal parameter lists of A and B are identical, and A has the same accessors as B (A is permitted to have additional accessors if it is not an explicit interface member implementation).

修改方式如下:is modified as follows:

基於介面對應的目的,類別成員會 A 在下列情況下符合介面成員 BFor purposes of interface mapping, a class member A matches an interface member B when:

  • AB 都是方法,且和的名稱和型式參數清單 A B 完全相同,而且的傳回型別 A 會透過 B 隱含參考轉換 B 的身分識別,轉換成的傳回型別。A and B are methods, and the name and formal parameter lists of A and B are identical, and the return type of A is convertible to the return type of B via an identity of implicit reference convertion to the return type of B.
  • A 而且 B 是屬性、和的名稱 A B 相同、 A 具有相同的存取子, B (因為 A 它不是明確的介面成員執行) ,而且的型別可透過身分 A 識別轉換轉換成的傳回型別, B 或者,如果 A 是 readonly 屬性,則隱含參考轉換。A and B are properties, the name of A and B are identical, A has the same accessors as B (A is permitted to have additional accessors if it is not an explicit interface member implementation), and the type of A is convertible to the return type of B via an identity conversion or, if A is a readonly property, an implicit reference conversion.
  • AB 是事件,而且和的名稱和類型 A B 都相同。A and B are events, and the name and type of A and B are identical.
  • AB 是索引子、和的型式參數清單 A B 相同、 A 具有相同的存取子, B (因為 A 它不是明確的介面成員執行) ,而且的型別可透過身分 A 識別轉換轉換成的傳回型別, B 或者,如果 A 是唯讀索引子,則隱含參考轉換。A and B are indexers, the formal parameter lists of A and B are identical, A has the same accessors as B (A is permitted to have additional accessors if it is not an explicit interface member implementation), and the type of A is convertible to the return type of B via an identity conversion or, if A is a readonly indexer, an implicit reference conversion.

這在技術上是一項重大變更,因為以下程式會列印「C1」。M 「今天,但是會列印」 C2。M」在建議的修訂下。This is technically a breaking change, as the program below prints "C1.M" today, but would print "C2.M" under the proposed revision.

using System;

interface I1 { object M(); }
class C1 : I1 { public object M() { return "C1.M"; } }
class C2 : C1, I1 { public new string M() { return "C2.M"; } }
class Program
{
    static void Main()
    {
        I1 i = new C2();
        Console.WriteLine(i.M());
    }
}

由於這種重大變更,我們可能會考慮在隱含的執行上不支援協變數傳回類型。Due to this breaking change, we might consider not supporting covariant return types on implicit implementations.

介面執行的條件約束Constraints on Interface Implementation

我們需要一個規則,明確的介面實作為其基底介面中任何覆寫所宣告的傳回型別,都必須宣告傳回型別。We will need a rule that an explicit interface implementation must declare a return type no less derived than the return type declared in any override in its base interfaces.

API 相容性的含意API Compatibility Implications

TBDTBD

開啟的問題Open Issues

此規格不會指出呼叫端如何取得更精簡的傳回型別。The specification does not say how the caller gets the more refined return type. 有可能會以類似于呼叫端取得最衍生之覆寫參數規格的方式來完成。Presumably that would be done in a way similar to the way that callers get the most derived override's parameter specifications.


如果我們有下列介面:If we have the following interfaces:

interface I1 { I1 M(); }
interface I2 { I2 M(); }
interface I3: I1, I2 { override I3 M(); }

請注意,在中 I3 ,方法 I1.M() 和都已「 I2.M() 合併」。Note that in I3, the methods I1.M() and I2.M() have been “merged”. 在實施時 I3 ,必須同時執行兩者。When implementing I3, it is necessary to implement them both together.

一般而言,我們需要明確的實作為參考原始方法。Generally, we require an explicit implementation to refer to the original method. 問題是在類別中The question is, in a class

class C : I1, I2, I3
{
    C IN.M();
}

這是什麼意思?What does that mean here? N 應該是什麼?What should N be?

我建議我們建議您同時執行 I1.MI2.M (,但不能同時進行這兩個) ,並將其視為兩者的實作為。I suggest that we permit implementing either I1.M or I2.M (but not both), and treat that as an implementation of both.

缺點Drawbacks

  • [] 每種語言變更都必須支付自己的費用。[ ] Every language change must pay for itself.
  • [] 我們應該確保效能合理,即使是深層繼承階層也一樣[ ] We should ensure that the performance is reasonable, even in the case of deep inheritance hierarchies
  • [] 我們應該確保轉譯策略的成品不會影響語言語義,即使使用舊編譯器的新 IL 也是一樣。[ ] We should ensure that artifacts of the translation strategy do not affect language semantics, even when consuming new IL from old compilers.

替代方案Alternatives

我們可以稍微放寬語言規則以允許,在來源中We could relax the language rules slightly to allow, in source,

abstract class Cloneable
{
    public abstract Cloneable Clone();
}

class Digit : Cloneable
{
    public override Cloneable Clone()
    {
        return this.Clone();
    }

    public new Digit Clone() // Error: 'Digit' already defines a member called 'Clone' with the same parameter types
    {
        return this;
    }
}

未解決的問題Unresolved questions

  • [] 如何使用此功能編譯的 Api 適用于較舊版本的語言?[ ] How will APIs that have been compiled to use this feature work in older versions of the language?

設計會議Design meetings