共用方式為


部分事件和建構函式

備註

本文是功能規格。 規格可作為功能的設計檔。 其中包含建議的規格變更,以及功能設計和開發期間所需的資訊。 這些文章會發佈,直到提議的規格變更完成並併併入目前的ECMA規格為止。

功能規格與已完成實作之間可能有一些差異。 這些差異已記錄在相關的 語言設計會議(LDM)備忘錄中。

您可以在 規範的文章中深入瞭解將功能規範納入 C# 語言標準的過程。

Champion 期數:https://github.com/dotnet/csharplang/issues/9058

總結

partial允許事件和建構函式上的修飾詞分隔宣告和實作元件,類似於部分方法和部分屬性/索引器

partial class C
{
    partial C(int x, string y);
    partial event Action<int, string> MyEvent;
}

partial class C
{
    partial C(int x, string y) { }
    partial event Action<int, string> MyEvent
    {
        add { }
        remove { }
    }
}

動機

C# 已經支援部分方法、屬性和索引器。 遺漏部分事件和建構函式。

部分事件對於弱事件庫很有用,用戶可以在其中撰寫定義:

partial class C
{
    [WeakEvent]
    partial event Action<int, string> MyEvent;

    void M()
    {
        RaiseMyEvent(0, "a");
    }
}

函式庫提供的程式碼產生器會提供實作:

partial class C
{
    private readonly WeakEvent _myEvent;

    partial event Action<int, string> MyEvent
    {
        add { _myEvent.Add(value); }
        remove { _myEvent.Remove(value); }
    }

    protected void RaiseMyEvent(int x, string y)
    {
        _myEvent.Invoke(x, y);
    }
}

部分事件和部分建構函式也適用於產生 Interop 程式代碼,例如在 Xamarin,用戶可以在其中撰寫部分建構函式和事件定義:

partial class AVAudioCompressedBuffer : AVAudioBuffer
{
    [Export("initWithFormat:packetCapacity:")]
    public partial AVAudioCompressedBuffer(AVAudioFormat format, uint packetCapacity);

    [Export("create:")]
    public partial event EventHandler Created;
}

而來源產生器會產生系結(在此情況下為 Objective-C):

partial class AVAudioCompressedBuffer : AVAudioBuffer
{
    [BindingImpl(BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]
    public partial AVAudioCompressedBuffer(AVAudioFormat format, uint packetCapacity) : base(NSObjectFlag.Empty)
    {
        // Call Objective-C runtime:
        InitializeHandle(
            global::ObjCRuntime.NativeHandle_objc_msgSendSuper_NativeHandle_UInt32(
                this.SuperHandle,
                Selector.GetHandle("initWithFormat:packetCapacity:"),
                format.GetNonNullHandle(nameof(format)),
                packetCapacity),
            "initWithFormat:packetCapacity:");
    }

    public partial event EventHandler Created
    {
        add { /* ... */ }
        remove { /* ... */ }
    }
}

詳細設計

一般

事件宣告語法 ({15.8.1) 已擴充,以允許 partial 修飾詞:

 event_declaration
-    : attributes? event_modifier* 'event' type variable_declarators ';'
+    : attributes? event_modifier* 'partial'? 'event' type variable_declarators ';'
-    | attributes? event_modifier* 'event' type member_name
+    | attributes? event_modifier* 'partial'? 'event' type member_name
         '{' event_accessor_declarations '}'
     ;

實例建構函式宣告語法(§15.11.1)已擴充,以允許 partial 修飾詞:

 constructor_declaration
-    : attributes? constructor_modifier* constructor_declarator constructor_body
+    : attributes? constructor_modifier* 'partial'? constructor_declarator constructor_body
     ;

請注意,有一項 提案,允許 partial 修飾詞可以放置在所有其他修飾詞間的任何位置,而不僅僅是作為最後一個修飾詞(也適用於方法、屬性和型別宣告)。

具有 partial 修飾詞的事件宣告據說是 部分事件宣告 ,而且它與一或多個 具有指定名稱的部分事件 相關聯(請注意,沒有存取子的事件宣告可以定義多個事件)。

具有 修飾詞的 partial 建構函式宣告據說是 部分建構函式宣告 ,而且它與 具有指定簽章的部分建構 函式相關聯。

當部分事件宣告指定或具有event_accessor_declarations修飾詞時,則稱為extern。 否則,這是一個定義性宣告。

部分建構函式宣告在具有分號主體且缺少 修飾詞時,即為extern。 否則,這是 的實作宣告

partial class C
{
    // defining declarations
    partial C();
    partial C(int x);
    partial event Action E, F;

    // implementing declarations
    partial C() { }
    partial C(int x) { }
    partial event Action E { add { } remove { } }
    partial event Action F { add { } remove { } }
}

只有部分成員的定義宣告會參與查找,並且在使用位置和產生元數據時被考慮。 (文件註釋如下所述除外。)實作宣告簽章用於相關程式本體的可為空分析。

部分性事件或部分性建構函式:

  • 僅能宣告為部分類型的成員。
  • 必須有一個定義和一個實作宣告。
  • 不允許有 abstract 修飾詞。
  • 無法明確實作介面成員。

部分事件不是類似欄位的事件(~15.8.2),亦即:

  • 它沒有任何編譯程式所產生的備份記憶體或存取子。
  • 它只能在 +=-= 作業中使用,而不是作為值。

定義部分建構函式宣告不能有建構函式初始化表示式 (: this(): base(); •15.11.2)。

解析暫停

partial 在更多情境中允許使用修飾詞是一項重大變更:

class C
{
    partial F() => new partial(); // previously a method, now a constructor
    @partial F() => new partial(); // workaround to keep it a method
}

class partial { }

為了簡化語言剖析器,即使我們沒有明確指定上述的文法變更,partial 修飾詞也會被接受於任何類似方法的宣告中(亦即本機函式和最上層腳本方法)。

屬性

所產生事件或建構函式的屬性是對應位置中部分宣告的結合屬性。 結合的屬性會以隨機未指定的順序串連,且不會移除重複項目。

部分事件宣告上會被忽略methodattribute_target§22.3)。 存取子屬性只能從存取子宣告中使用(它們只能出現在實作宣告下)。 請注意, 在所有事件宣告中,param 以及 returnattribute_target 都已被忽略。

實作宣告上的呼叫端資訊屬性會由編譯程式忽略,如 Caller-info 屬性一節中部分屬性提案所指定(請注意,它會套用至包含部分事件和建構函式的所有部分成員)。

簽名

部分成員的這兩個宣告都必須有類似部分屬性的相符簽章

  1. 部分宣告中的類型和 ref 類型(kind) 差異會在執行時產生影響,從而導致編譯時錯誤。
  2. 部分宣告之間的 Tuple 元素名稱不一致會導致編譯時錯誤。
  3. 宣告必須具有相同修飾詞,不過修飾詞可能會以不同的順序出現。
    • 例外狀況:這不適用於 extern 只能在實作宣告上出現的修飾詞。
  4. 部分宣告的簽章中所有其他語法差異會導致編譯時警告,但有下列例外狀況:
    • 屬性清單不需要符合上述內容
    • 可為 Null 的內容差異(例如遺忘與批注)不會造成警告。
    • 默認參數值不需要相符,但是當實作建構函式宣告具有預設參數值時,就會報告警告(因為只有定義宣告只參與查閱,因此會忽略這些值)。
  5. 當參數名稱在定義和實作建構子宣告時不同,就會發出警告。
  6. 不涉及模糊 null 性的 null 性差異會導致警告。

文件註解

允許在定義和實作宣告中都包含文件註釋。 請注意,事件存取子不支援文件註解。

當文件註解僅存在於部分成員的一個宣告時,這些文件註解會被正常使用(透過 Roslyn API 呈現,並輸出到文件 XML 檔案中)。

當檔批注同時存在於部分成員的這兩個宣告上時,會卸除定義宣告上的所有檔批注,而且只會使用實作宣告上的檔批注。

當部分成員的宣告間參數名稱不同時, paramref 元素會使用原始碼中與檔批註相關聯的宣告的參數名稱。 例如,放置在實作宣告上的文件批註中的paramref,會使用參數名稱來參考實作宣告的參數符號。 這可能會造成混淆,因為元數據簽章會使用定義宣告中的參數名稱。 建議您確保參數名稱在局部成員的宣告中保持一致,以避免這種混淆。

未解決的問題

成員類型

我們想要部分事件、建構函式、運算元、欄位嗎? 我們建議前兩個成員類型,但可以考慮任何其他子集。

您也可以考慮部分 主要 建構函式,例如,允許使用者在多個部分類型宣告上擁有相同的參數清單。

屬性位置

我們應該辨識 [method:] 屬性目標規範,適用於部分事件(或者僅限於定義宣告)嗎? 然後,所生成的存取子屬性會是來自定義(或僅是定義)宣告部分的 method 目標屬性,及來自執行宣告存取子的自我目標屬性和 method 目標屬性的串接。 合併不同宣告類型的屬性是史無前例的,事實上,Roslyn 中目前屬性比對的實作並不支援這一點。

我們也可以考慮辨識 [param:][return:],而不只是在部分事件上,而是所有類似字段的事件和 extern 事件。 如需詳細資訊,請參閱 https://github.com/dotnet/roslyn/issues/77254