默認介面方法
注意
本文是功能規格。 規格可作為功能的設計檔。 其中包含建議的規格變更,以及功能設計和開發期間所需的資訊。 這些文章會發佈,直到提議的規格變更完成並併併入目前的ECMA規格為止。
功能規格與已完成實作之間可能有一些差異。 這些差異被記錄在相關的 語言設計會議(LDM)筆記中。
您可以在介紹 規格的文章中,深入瞭解將功能規範納入 C# 語言標準的過程。
冠軍問題:https://github.com/dotnet/csharplang/issues/52
總結
新增 虛擬擴充方法的支援 - 具有具體實作之介面中的方法。 實作這類介面的類別或結構,要求必須有介面方法的單一 最具體的 實作,這可以是由該類別或結構自身實作,或是從其基類或介面繼承而來。 虛擬擴充方法可讓 API 作者在未來版本中將方法新增至介面,而不會中斷與該介面現有實作的來源或二進位相容性。
這些類似於 Java “Default Methods”。
(根據可能實作的技術)這項功能需要 CLI/CLR 中的對應支援。 利用這項功能的程式無法在舊版的平台上執行。
動機
此功能的主要動機為
- 默認介面方法可讓 API 作者在未來版本中將方法新增至介面,而不會中斷與該介面現有實作的來源或二進位相容性。
- 此功能可讓 C# 與以 Android (Java) 和 iOS (Swift)為目標的 API 互通,其支持類似的功能。
- 事實證明,新增預設介面實作會提供「特性」語言功能的元素(https://en.wikipedia.org/wiki/Trait_(computer_programming))。 特性已證明是一種功能強大的程式設計技術(http://scg.unibe.ch/archive/papers/Scha03aTraits.pdf)。
詳細設計
介面的語法被擴展以允許
- 宣告常數、運算符、靜態建構函式和巢狀類型的成員宣告;
- 方法或索引器、屬性或事件存取子的 主體(也就是「預設」實作):
- 宣告靜態欄位、方法、屬性、索引器和事件的成員宣告;
- 使用明確介面實作語法的成員宣告;和
- 明確的存取修飾詞(預設存取層級是
public
)。
具有主體的成員使介面能在沒有自行實作方法的類別和結構中提供「預設」的實作。
介面可能不包含實例狀態。 雖然現在允許靜態欄位,但介面中不允許實例欄位。 介面不支持實例自動屬性,因為它們會隱含宣告隱藏欄位。
靜態和私用方法允許用來實作介面公用 API 的程式代碼的實用重構和組織。
介面中的方法覆寫必須使用明確的介面實作語法。
在以 variance_annotation宣告的類型參數範圍內,宣告類別類型、結構類型或列舉類型是錯誤的。 例如,以下 C
的宣告是錯誤。
interface IOuter<out T>
{
class C { } // error: class declaration within the scope of variant type parameter 'T'
}
介面中的具體方法
這項功能最簡單的形式是能夠在介面中宣告 具體方法,這是具有主體的方法。
interface IA
{
void M() { WriteLine("IA.M"); }
}
實作這個介面的類別不需要實作其具體方法。
class C : IA { } // OK
IA i = new C();
i.M(); // prints "IA.M"
在類別 IA.M
中,C
的最終覆寫是 M
中宣告的具體方法 IA
。 請注意,類別不會從其介面繼承成員,此特性並未改變:
new C().M(); // error: class 'C' does not contain a member 'M'
在介面的實例成員內,this
具有封入介面的類型。
介面中的修飾詞
介面的語法會放寬,以允許其成員的修飾詞。 允許下列專案:private
、protected
、internal
、public
、virtual
、abstract
、sealed
、static
、extern
和 partial
。
介面成員,其宣告包含主體是 virtual
成員,除非使用 sealed
或 private
修飾詞。
virtual
修飾詞可用於通常會被隱含地 virtual
的函式成員。 同樣地,雖然 abstract
是沒有實作內容的介面成員的預設值,但該修飾詞也可以被明確指定。 非虛擬成員可以使用 sealed
關鍵詞來宣告。
介面中的功能成員 private
或 sealed
沒有主體是錯誤的。
private
函式成員可能沒有 修飾詞 sealed
。
存取修飾詞可以用於所有允許的介面成員。 存取層級 public
是預設值,但可能會明確指定。
開放問題: 我們需要指定存取修飾詞的精確意義,例如
protected
和internal
,以及哪些宣告會執行和不覆寫它們(在衍生介面中),或實作它們(在實作 介面的類別中)。
介面可以宣告 static
成員,包括巢狀類型、方法、索引器、屬性、事件和靜態建構函式。 所有介面成員的預設存取層級 public
。
介面可能不會宣告實例建構函式、解構函式或欄位。
已關閉的問題: 介面中是否允許運算符宣告? 可能不是轉換運算符,那其他運算符呢? 決策:允許的運算符 ,但不包括轉換、相等和不等運算子 。
已關閉的問題: 是否應該允許在隱藏基底介面成員的介面成員宣告上
new
? 決策:是的。
已關閉的問題: 我們目前不允許在介面或其成員上
partial
。 這將需要另外一個提案。 決策:是的。 https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#permit-partial-in-interface
介面中的明確實作
明確實作可讓程式設計人員在編譯程式或運行時間找不到虛擬成員的介面中提供最特定的虛擬成員實作。 允許實作宣告 明確地 使用介面名稱限定宣告來實作特定的基底介面方法(在此情況下不允許任何存取修飾詞)。 不允許隱含實作。
interface IA
{
void M() { WriteLine("IA.M"); }
}
interface IB : IA
{
void IA.M() { WriteLine("IB.M"); } // Explicit implementation
}
interface IC : IA
{
void M() { WriteLine("IC.M"); } // Creates a new M, unrelated to `IA.M`. Warning
}
介面中的顯式實作不應該宣告在 sealed
。
介面中的公用 virtual
函式成員只能明確地在衍生介面中實作(藉由將原本宣告方法之介面類型的名稱限定為宣告中的名稱,以及省略存取修飾詞)。 成員必須 實作的可存取。
重抽象化
在介面中宣告的虛擬(具體)方法可能會在衍生介面中重新抽象化。
interface IA
{
void M() { WriteLine("IA.M"); }
}
interface IB : IA
{
abstract void IA.M();
}
class C : IB { } // error: class 'C' does not implement 'IA.M'.
abstract
宣告中需要 IB.M
修飾詞,以指出 IA.M
正在重新抽象化。
這在衍生介面中很有用,其中方法的默認實作不適當,而且實作類別應該提供更適當的實作。
最特定的實作規則
我們要求每個介面和類別在其型別或直接和間接介面中對於每個虛擬成員都有一個 最具體的實作。 最特定的實作 是唯一的實作,比其他實作更具體。 如果沒有實作,成員本身會被視為最特定的實作。
M1
如果 在類型 M2
上宣告,M1
在類型 T1
上宣告,M1
在類型 T1
上宣告,則會將一個實作 比另一個實作 更具體。
-
T1
包含T2
在其直接或間接的介面中,或 -
T2
是介面類型,但T1
不是介面類型。
例如:
interface IA
{
void M() { WriteLine("IA.M"); }
}
interface IB : IA
{
void IA.M() { WriteLine("IB.M"); }
}
interface IC : IA
{
void IA.M() { WriteLine("IC.M"); }
}
interface ID : IB, IC { } // compiles, but error when a class implements 'ID'
abstract class C : IB, IC { } // error: no most specific implementation for 'IA.M'
abstract class D : IA, IB, IC // ok
{
public abstract void M();
}
public class E : ID { } // Error. No most specific implementation for 'IA.M'
最具體的實作規則可確保衝突(即鑽石繼承產生的模棱兩可)是由程式設計人員在衝突發生時明確解決。
由於我們在介面中支持明確的再抽象化,因此我們也可以在類別中執行這樣的操作。
abstract class E : IA, IB, IC // ok
{
abstract void IA.M();
}
已關閉的問題:我們應該在類別中支持明確的介面抽象實作嗎? 決策:否
此外,如果在類別宣告中,某些介面方法最特定的實作是介面中宣告的抽象實作,就會發生錯誤。 這是使用新術語重新整理的現有規則。
interface IF
{
void M();
}
abstract class F : IF { } // error: 'F' does not implement 'IF.M'
在介面中宣告的虛擬屬性,可能在某一介面中對其 get
存取子有最具體的實作,而在不同的介面中,對其 set
存取子有另外一個最具體的實作。 這被視為違反 最特定的實作 規則,併產生編譯程序錯誤。
static
和 private
方法
因為介面現在可能包含可執行的程式代碼,所以將一般程式代碼抽象化為私用和靜態方法很有用。 我們現在允許在介面中使用這些。
關閉的問題:我們應該支援私有方法嗎? 我們應該支持靜態方法嗎? 決策:是
開放問題:我們是否應允許介面的方法是
protected
、internal
或其他存取權? 如果是,語意是什麼? 它們是否預設為virtual
嗎? 如果是,有辦法讓他們成為非虛擬嗎?
已經關閉的問題:如果我們支援靜態方法,我們應該支援(靜態)運算子嗎? 決策:是
基底介面呼叫
本節中的語法尚未實作。 它仍然是一個積極的建議。
衍生自具有預設方法之介面之型別中的程式代碼可以明確地叫用該介面的「基底」實作。
interface I0
{
void M() { Console.WriteLine("I0"); }
}
interface I1 : I0
{
override void M() { Console.WriteLine("I1"); }
}
interface I2 : I0
{
override void M() { Console.WriteLine("I2"); }
}
interface I3 : I1, I2
{
// an explicit override that invoke's a base interface's default method
void I0.M() { I2.base.M(); }
}
實例(非靜態)方法允許使用語法 base(Type).M
,以非虛擬方式叫用直接基底介面中可存取實例方法的實作。 當需要基於菱形繼承提供的覆寫,可以通過委派給特定的基礎實作來解決時,這將非常有用。
interface IA
{
void M() { WriteLine("IA.M"); }
}
interface IB : IA
{
override void IA.M() { WriteLine("IB.M"); }
}
interface IC : IA
{
override void IA.M() { WriteLine("IC.M"); }
}
class D : IA, IB, IC
{
void IA.M() { base(IB).M(); }
}
在使用語法 virtual
存取 abstract
或 base(Type).M
成員時,要求 Type
包含一個針對 的最特定且唯一的 M
。
系結基底子句
介面現在包含類型。 這些類型可用於基底子句作為基底介面。 綁定基礎子句時,我們可能需要了解一組基礎介面,以便綁定這些類型(例如在這些介面中查找並解決受保護的存取權)。 因此,介面基底子句的意義會循環定義。 為了打破這個循環,我們新增一套新的語言規則,這些規則與已經存在於類別中的類似規則相對應。
在判斷介面 interface_base 的意義時,基底介面會暫時假設為空白。 從直覺上看,這可確保基底子句的意義不能以遞歸方式相依於本身。
我們過去曾有下列規則:
「當類別 B 衍生自類別 A 時,若 A 相依於 B,則會發生編譯時期錯誤。類別 直接相依於其直接基類(如果有的話),而類別 直接相依於其直接巢狀於其中的類別(如果有的話)。」 鑒於此定義,類別 所依賴的完整類別集合 是 直接依賴於 關係的自反性和傳遞性閉包。
這是介面直接或間接繼承自本身的編譯時間錯誤。
介面 的
我們正在調整它們,如下所示:
當類別 B 衍生自類別 A 時,若 A 相依於 B,則會發生編譯時期錯誤。類別 直接相依於 其直接的基底類別(如果有的話),而 直接相依於類型,在其中立即巢狀的型別(如果有的話)。
當介面 IB 擴充介面 IA 時,如果 IA 相依於 IB,則會產生編譯錯誤。 介面 直接相依於 其直接基底介面(如果有的話)和 直接相依於 其直接巢狀類型(如果有的話)。
鑒於這些定義,類型相依
對現有程序的影響
此處呈現的規則旨在對現有程式的意義沒有任何影響。
範例 1:
interface IA
{
void M();
}
class C: IA // Error: IA.M has no concrete most specific override in C
{
public static void M() { } // method unrelated to 'IA.M' because static
}
範例 2:
interface IA
{
void M();
}
class Base: IA
{
void IA.M() { }
}
class Derived: Base, IA // OK, all interface members have a concrete most specific override
{
private void M() { } // method unrelated to 'IA.M' because private
}
相同的規則會提供類似與涉及預設介面方法的類似情況的結果:
interface IA
{
void M() { }
}
class Derived: IA // OK, all interface members have a concrete most specific override
{
private void M() { } // method unrelated to 'IA.M' because private
}
已關閉的問題:確認這是規格的預期結果。 決策:是
運行時方法解析
已關閉的問題: 規格應該描述在面對介面預設方法時的執行時期方法解析演算法。 我們需要確保語義與語言語義一致,例如哪些宣告的方法會覆寫或實作
internal
方法,以及哪些不會。
CLR 支援 API
為了讓編譯器識別它們正在針對支援此功能的執行時期進行編譯,此類執行時期的庫會修改為透過 https://github.com/dotnet/corefx/issues/17116中所討論的 API 宣告此情況。 我們新增
namespace System.Runtime.CompilerServices
{
public static class RuntimeFeature
{
// Presence of the field indicates runtime support
public const string DefaultInterfaceImplementation = nameof(DefaultInterfaceImplementation);
}
}
開啟問題:這是 CLR 功能的最佳名稱嗎? CLR 功能不僅如此(例如放寬保護限制、支持接口中的覆蓋等)。 也許它應該稱為「介面中的具體方法」或「特性」?
要指定進一步的區域
- 將預設介面方法和覆寫新增至現有介面所造成的源代碼和二進制相容性影響進行類型整理會很有用。
缺點
此提案需要 CLR 規格的協調更新(以支援介面和方法解析中的具體方法)。 因此,它相當「昂貴」,但如果與我們預期也需要 CLR 變更的其他功能結合使用,可能會值得這麼做。
替代方案
沒有。
未解決的問題
- 上述提案中都會提出公開問題。
- 另請參閱 https://github.com/dotnet/csharplang/issues/406 以取得公開問題清單。
- 詳細規格必須描述運行時間所使用的解析機制,以選取要叫用的精確方法。
- 需要詳細研究新編譯器產生的中繼資料與舊版編譯器取用之間的互動。 例如,我們需要確保使用的元數據表示法不會造成介面中新增預設實作,以中斷由舊版編譯程式編譯時實作該介面的現有類別。 這可能會影響我們可以使用的元數據表示法。
- 設計必須考慮與其他語言的互操作,以及其他語言的現有編譯程式。
已解決的問題
抽象覆寫
先前的草稿規格包含「重新建構」繼承方法的能力:
interface IA
{
void M();
}
interface IB : IA
{
override void M() { }
}
interface IC : IB
{
override void M(); // make it abstract again
}
我2017-03-20的筆記顯示,我們決定不允許這樣做。 不過,至少有兩個使用案例:
- 希望與這項功能互操作的某些使用者所使用的 Java API 依賴於此設施。
- 使用 特性的程式設計因此讓 獲益。 重抽象是「特性」語言功能的其中一個元素(https://en.wikipedia.org/wiki/Trait_(computer_programming))。 以下是允許與類別相關的內容:
public abstract class Base
{
public abstract void M();
}
public abstract class A : Base
{
public override void M() { }
}
public abstract class B : A
{
public override abstract void M(); // reabstract Base.M
}
不幸的是,除非允許,否則此程式碼無法重構為一組介面(特性)。 根據 Jared 的貪婪原則,這應該被允許。
已關閉的問題: 是否應該允許重新抽象化? [是]我的筆記錯了。 LDM 附註 指出,介面中允許重新抽象化。 不在類別中。
虛擬修飾符與密封修飾符
來自 阿列克謝·齊加烏茲:
我們決定允許在介面成員上明確陳述修飾詞,除非有理由不允許其中一些。 這帶來了虛擬修飾詞的有趣問題。 是否應該在具有預設實作的成員上需要它?
我們可以說:
- 如果既沒有實作,也沒有指定 virtual 或 sealed,我們會假設該成員是抽象的。
- 如果有實作,而且未指定抽象或密封,我們假設成員是虛擬的。
- 密封修飾詞必須讓方法既不是虛擬,也不是抽象。
或者,我們可以說虛擬成員需要虛擬修飾詞。 亦即,如果已實現的成員未明顯地標示為虛擬修飾詞,則它既不是虛擬,也不是抽象。 當方法從類別移至介面時,此方法可能會提供更好的體驗:
- 抽象方法會保持抽象。
- 虛擬方法始終是虛擬的。
- 沒有任何修飾詞的方法既不是虛擬的,也不是抽象的。
- sealed 修飾詞無法套用至不是覆寫的函式。
你覺得怎麼樣?
問題已結案: 具體方法(含實作)是否應隱含
virtual
? [是]
決定:在LDM 2017-04-05 中做出的:
- 非虛擬應該透過
sealed
或private
明確表示。 -
sealed
是讓介面實例成員具有非虛擬主體的關鍵詞 - 我們想要允許介面中的所有修飾符
- 介面成員的預設可存取性為公用,包括巢狀類型
- 介面中的 private 函式成員會隱含密封,且不允許
sealed
。 - 私人類別(在介面中)是允許的,並且可以封閉,這表示封閉在類別意義上的封閉。
- 如果沒有良好的建議,介面或其成員仍然不允許部分。
二進位相容性 1
當程式庫提供預設實作時
interface I1
{
void M() { Impl1 }
}
interface I2 : I1
{
}
class C : I2
{
}
我們瞭解,I1.M
中的 C
實作是 I1.M
。 如果包含 I2
的元件變更如下並重新編譯,該怎麼辦
interface I2 : I1
{
override void M() { Impl2 }
}
C
並未被重新編譯。 執行程式時會發生什麼事? 調用 (C as I1).M()
- 執行
I1.M
- 執行
I2.M
- 擲回某種運行時錯誤
決策: 做出於 2017-04-11:執行 I2.M
,這是在運行時間中最具體且毫無歧義的覆寫。
事件存取子 (已關閉)
已關閉的問題: 事件是否可以被覆寫為「分段」?
請考慮此案例:
public interface I1
{
event T e1;
}
public interface I2 : I1
{
override event T
{
add { }
// error: "remove" accessor missing
}
}
不允許此事件的「部分」實作,因為在類別中,事件宣告的語法不允許只提供一個存取子,必須同時提供兩個存取子(或兩者都不提供)。 您可以藉由允許語法中的抽象 remove 存取子在缺少主體時隱含地抽象完成相同的動作:
public interface I1
{
event T e1;
}
public interface I2 : I1
{
override event T
{
add { }
remove; // implicitly abstract
}
}
請注意,這是新的 (建議) 語法。 在目前的文法中,事件存取子必須有一個具體的主體。
已結案問題: 事件存取子是否可以因為缺少主體而隱含地成為抽象,就像介面的方法和屬性存取子因為缺少主體而隱含地成為抽象一樣?
決定: (2017-04-18) 否,事件宣告需要兩個具體的存取子(或者兩者都沒有)。
類別中的重新抽象 (已關閉)
已關閉的問題: 我們應該確認這是允許的(否則新增預設實現將是重大變更)。
interface I1
{
void M() { }
}
abstract class C : I1
{
public abstract void M(); // implement I1.M with an abstract method in C
}
決策: (2017-04-18) 是,將主體新增至介面成員宣告不應中斷 C。
密封覆寫 (已關閉)
前一個問題隱含假設 sealed
修飾詞可以套用至介面中的 override
。 這與草稿規格相矛盾。 我們想要允許封存覆寫嗎? 應考慮密封的來源和二進位相容性效果。
已關閉的問題:是否應該允許封存覆寫?
決策: (2017-04-18)禁止在介面中覆寫 sealed
。 在介面成員上,sealed
的唯一用途是在其最初宣告中使其成為非虛擬的。
鑽石繼承和類別(封閉)
提案草案偏向於在菱形繼承情形中使用類別覆蓋,而非介面覆蓋。
我們要求,每個介面和類別的每個介面方法都必須在類型或其直接和間接介面中的覆寫裡有一個 最具體的覆寫。 最特定的覆寫 是唯一的覆寫,比其他覆寫更具體。 如果沒有其他覆寫,則該方法本身被視為最具特定性的覆寫。
一個覆寫
M1
M2
, 如果M1
在類型T1
上宣告,M2
在類型T2
上宣告,而且任一條件成立。
T1
包含T2
在其直接或間接的介面中,或T2
是介面類型,但T1
不是介面類型。
此案例為
interface IA
{
void M();
}
interface IB : IA
{
override void M() { WriteLine("IB"); }
}
class Base : IA
{
void IA.M() { WriteLine("Base"); }
}
class Derived : Base, IB // allowed?
{
static void Main()
{
IA a = new Derived();
a.M(); // what does it do?
}
}
我們應該確認此行為(或做出其他決定)
已關閉的問題: 確認以上草稿規格,在應用於混合類別和介面時,類別優先於介面。 請參閱 https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-19.md#diamonds-with-classes。
介面方法與結構(已關閉)
默認介面方法和結構之間有一些不幸的互動。
interface IA
{
public void M() { }
}
struct S : IA
{
}
請注意,介面成員不會繼承:
var s = default(S);
s.M(); // error: 'S' does not contain a member 'M'
因此,客戶端必須方塊結構以叫用介面方法
IA s = default(S); // an S, boxed
s.M(); // ok
以這種方式拳擊擊敗了 struct
類型的主要優點。 此外,任何突變方法都不會有明顯的效果,因為它們是在結構 boxed 複製 作業:
interface IB
{
public void Increment() { P += 1; }
public int P { get; set; }
}
struct T : IB
{
public int P { get; set; } // auto-property
}
T t = default(T);
Console.WriteLine(t.P); // prints 0
(t as IB).Increment();
Console.WriteLine(t.P); // prints 0
已關閉的問題: 我們可以怎麼辦?
- 禁止
struct
繼承預設實作。 在struct
中,所有介面的方法都會被視為抽象。 然後,我們可以花一些時間來決定如何讓它運作得更好。- 想出一種避免 boxing 的程式碼產生策略。 在
IB.Increment
之類的方法內,this
的類型可能類似於限制為IB
的類型參數。 此外,為了避免呼叫者覆箱化,非抽象方法會從介面繼承。 這可能會大幅增加編譯程式和CLR實作的工作。- 不需擔心它,就把它當成一個疣。
- 其他想法?
決定: 不要擔心它,只需把它當作一顆疣即可。 請參閱 https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-19.md#structs-and-default-implementations。
基底介面呼叫 (已關閉)
此決定未在 C# 8 中實作。 不會實作 base(Interface).M()
語法。
草稿規格建議以 Java 為靈感的基底介面調用語法:Interface.base.M()
。 我們至少需要針對初始原型選取語法。 我的最愛是 base<Interface>.M()
。
已關閉的問題: 基底成員調用的語法為何?
決策: 語法是 base(Interface).M()
。 請參閱 https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-19.md#base-invocation。 名為的介面必須是基底介面,但不需要是直接基底介面。
待解決問題: 是否允許在類別成員中調用基底介面?
決策:是的。 https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-19.md#base-invocation
覆寫未公開介面成員(已關閉)
在介面中,基底介面的非公用成員會使用 override
修飾詞來覆寫。 如果它是命名包含成員之介面的「明確」覆寫,則會省略存取修飾詞。
已關閉的問題: 如果它是未命名介面的「隱含」覆寫,存取修飾詞是否必須相符?
決策: 只有公用成員才可被隱式覆寫,且存取權限必須相符。 請參閱 https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-18.md#dim-implementing-a-non-public-interface-member-not-in-list。
開啟問題: 在明確覆寫的情況下,存取修飾符是必要的、可選的,還是可以省略,例如
override void IB.M() {}
?
開啟問題:在 中,
override
是否在明確覆蓋時是必要的、可選的或要省略,例如void IB.M() {}
?
如何在類別中實作非公用介面成員? 也許必須明確地進行嗎?
interface IA
{
internal void MI();
protected void MP();
}
class C : IA
{
// are these implementations? Decision: NO
internal void MI() {}
protected void MP() {}
}
已關閉問題: 如何在類別中實作非公開介面成員?
決策: 您只能明確實施非公用介面成員。 請參閱 https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-18.md#dim-implementing-a-non-public-interface-member-not-in-list。
決策:不允許介面成員使用任何 override
關鍵詞。 https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#does-an-override-in-an-interface-introduce-a-new-member
二進位相容性 2 (已關閉)
請考慮下列程式碼,其中每個類型都位於不同的組件中
interface I1
{
void M() { Impl1 }
}
interface I2 : I1
{
override void M() { Impl2 }
}
interface I3 : I1
{
}
class C : I2, I3
{
}
我們瞭解,I1.M
中的 C
實作是 I2.M
。 如果包含 I3
的元件變更如下並重新編譯,該怎麼辦
interface I3 : I1
{
override void M() { Impl3 }
}
C
並未被重新編譯。 執行程式時會發生什麼事? 調用 (C as I1).M()
- 執行
I1.M
- 執行
I2.M
- 執行
I3.M
- 具決定性的 2 或 3
- 拋出某種執行時例外
決策:拋出例外狀況(5)。 請參閱 https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#issues-in-default-interface-methods。
介面中的 partial
是否許可? (關閉)
考慮到介面可能會以與抽象類相似的方式使用,因此可以在 partial
宣告它們以提高效用。 這在面對發電機時特別有用。
提案: 移除介面和介面成員不得在
partial
宣告的語言限制。
決策:是的。 請參閱 https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#permit-partial-in-interface。
介面中的 Main
? (關閉)
開啟問題: 介面中的
static Main
方法是否為程序入口點的潛在對象?
決策:是的。 請參閱 https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#main-in-an-interface。
確認支援公用非虛擬方法的意圖(已關閉)
我們是否可以確認或撤銷我們允許介面中非虛擬公用方法的決定?
interface IA
{
public sealed void M() { }
}
Semi-Closed 問題:(2017-04-18)我們認為這將是有用的,但將來會再回來檢視。 這是一個心理模型絆倒區塊。
介面中的 override
引進新成員嗎? (關閉)
有幾種方法可以觀察覆寫宣告是否引入新成員。
interface IA
{
void M(int x) { }
}
interface IB : IA
{
override void M(int y) { } // 'override' not permitted
}
interface IC : IB
{
static void M2()
{
M(y: 3); // permitted? Decision: No.
}
override void IB.M(int z) { } // permitted? What does it override? Decision: No.
}
開啟問題: 介面中的覆蓋宣告是否會帶入新的成員? (關閉)
在類別中,覆蓋方法從某種角度來看是「可見」的。 例如,其參數名稱相較於被覆寫的方法中的參數名稱更具優先權。 在介面中有可能複製這種行為,因為總會有一個最具體的覆寫。 但是我們想要複製這種行為嗎?
此外,是否可以「再次覆寫」覆寫方法? 無關緊要
決策:不允許介面成員使用任何 override
關鍵詞。
https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#does-an-override-in-an-interface-introduce-a-new-member。
具有私有取用器的屬性(封閉)
我們說私人成員不是虛擬成員,不允許虛擬和私用的組合。 但是,具有私有存取子的屬性呢?
interface IA
{
public virtual int P
{
get => 3;
private set { }
}
}
這是允許的嗎? 這裡的 set
存取子是否為 virtual
? 可以在可存取時覆寫嗎? 下列是否僅對 get
存取子進行隱式實作?
class C : IA
{
public int P
{
get => 4;
set { }
}
}
下列項目可能是錯誤,因為 IA.P.set 既不是虛擬的,也無法存取。
class C : IA
{
int IA.P
{
get => 4;
set { } // Decision: Not valid
}
}
決策:第一個範例看起來有效,而最後一個範例則無效。 這以類似於 C# 中運作的方式來解決。 https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#properties-with-a-private-accessor
基礎介面呼叫,第 2 輪 (已結束)
這未在 C# 8 中實作。
我們先前如何處理基底調用的「解決方案」實際上並沒有提供足夠的表達性。 事實證明,在 C# 和 CLR 中,不同於 Java,您需要同時指定包含方法宣告的介面,以及您想要叫用之實作的位置。
我針對介面中的基底呼叫提出下列語法。 我不愛它,但它說明瞭任何語法必須能夠表達什麼:
interface I1 { void M(); }
interface I2 { void M(); }
interface I3 : I1, I2 { void I1.M() { } void I2.M() { } }
interface I4 : I1, I2 { void I1.M() { } void I2.M() { } }
interface I5 : I3, I4
{
void I1.M()
{
base<I3>(I1).M(); // calls I3's implementation of I1.M
base<I4>(I1).M(); // calls I4's implementation of I1.M
}
void I2.M()
{
base<I3>(I2).M(); // calls I3's implementation of I2.M
base<I4>(I2).M(); // calls I4's implementation of I2.M
}
}
如果沒有模棱兩可,您可以更簡單地撰寫
interface I1 { void M(); }
interface I3 : I1 { void I1.M() { } }
interface I4 : I1 { void I1.M() { } }
interface I5 : I3, I4
{
void I1.M()
{
base<I3>.M(); // calls I3's implementation of I1.M
base<I4>.M(); // calls I4's implementation of I1.M
}
}
或
interface I1 { void M(); }
interface I2 { void M(); }
interface I3 : I1, I2 { void I1.M() { } void I2.M() { } }
interface I5 : I3
{
void I1.M()
{
base(I1).M(); // calls I3's implementation of I1.M
}
void I2.M()
{
base(I2).M(); // calls I3's implementation of I2.M
}
}
或
interface I1 { void M(); }
interface I3 : I1 { void I1.M() { } }
interface I5 : I3
{
void I1.M()
{
base.M(); // calls I3's implementation of I1.M
}
}
決定:決定 base(N.I1<T>).M(s)
,承認如果我們有調用系結,稍後可能會有問題。https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-11-14.md#default-interface-implementations
結構未實作預設方法的警告? (關閉)
@vancem 主張,我們應該認真考慮,在實值型別宣告無法覆寫某些介面方法時,即使它會從介面繼承該方法的實作,也要產生警告。 因為它會導致拳擊和破壞限制的呼叫。
決策:這似乎更適合分析師。 此警告似乎也會很吵鬧,因為即使從未呼叫預設介面方法,也不會發生任何 Boxing,也會引發此警告。 https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#warning-for-struct-not-implementing-default-method
介面靜態建構函式 (已關閉)
介面靜態建構函式何時執行? 目前的 CLI 草稿建議在存取第一個靜態方法或字段時發生。 如果兩者都不存在,那麼它可能永遠不會運行?
2018-10-09 CLR 小組建議「我們將模仿對值類型執行的操作」(在存取每個實例方法時進行 cctor 檢查)
決策:如果靜態建構函式未 beforefieldinit
,靜態建構函式也會在進入實例方法時執行,在此情況下,靜態建構函式會在存取第一個靜態欄位之前執行。 https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#when-are-interface-static-constructors-run
設計會議
2017-03-08 LDM 會議筆記2017-03-21 LDM 會議筆記2017-03-23 會議 “CLR 行為 預設介面方法”2017-04-05 LDM 會議筆記2017-04-11 LDM 會議筆記2017-04-18 LDM 會議筆記2017-04-19 LDM 會議筆記2017-05-17 LDM 會議筆記2017-05-31 LDM 會議筆記2017-06-14 LDM 會議筆記2018-10-17 LDM 會議筆記2018-11-14 LDM 會議筆記