備註
本文是功能規格。 規格可作為功能的設計檔。 其中包含建議的規格變更,以及功能設計和開發期間所需的資訊。 這些文章會發佈,直到提議的規格變更完成並併併入目前的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
修飾詞也會被接受於任何類似方法的宣告中(亦即本機函式和最上層腳本方法)。
屬性
所產生事件或建構函式的屬性是對應位置中部分宣告的結合屬性。 結合的屬性會以隨機未指定的順序串連,且不會移除重複項目。
部分事件宣告上會被忽略method
attribute_target(§22.3)。
存取子屬性只能從存取子宣告中使用(它們只能出現在實作宣告下)。
請注意, 在所有事件宣告中,param
以及 return
attribute_target 都已被忽略。
實作宣告上的呼叫端資訊屬性會由編譯程式忽略,如 Caller-info 屬性一節中部分屬性提案所指定(請注意,它會套用至包含部分事件和建構函式的所有部分成員)。
簽名
部分成員的這兩個宣告都必須有類似部分屬性的相符簽章:
- 部分宣告中的類型和 ref 類型(kind) 差異會在執行時產生影響,從而導致編譯時錯誤。
- 部分宣告之間的 Tuple 元素名稱不一致會導致編譯時錯誤。
- 宣告必須具有相同修飾詞,不過修飾詞可能會以不同的順序出現。
- 例外狀況:這不適用於
extern
只能在實作宣告上出現的修飾詞。
- 例外狀況:這不適用於
- 部分宣告的簽章中所有其他語法差異會導致編譯時警告,但有下列例外狀況:
- 屬性清單不需要符合上述內容。
- 可為 Null 的內容差異(例如遺忘與批注)不會造成警告。
- 默認參數值不需要相符,但是當實作建構函式宣告具有預設參數值時,就會報告警告(因為只有定義宣告只參與查閱,因此會忽略這些值)。
- 當參數名稱在定義和實作建構子宣告時不同,就會發出警告。
- 不涉及模糊 null 性的 null 性差異會導致警告。
文件註解
允許在定義和實作宣告中都包含文件註釋。 請注意,事件存取子不支援文件註解。
當文件註解僅存在於部分成員的一個宣告時,這些文件註解會被正常使用(透過 Roslyn API 呈現,並輸出到文件 XML 檔案中)。
當檔批注同時存在於部分成員的這兩個宣告上時,會卸除定義宣告上的所有檔批注,而且只會使用實作宣告上的檔批注。
當部分成員的宣告間參數名稱不同時, paramref
元素會使用原始碼中與檔批註相關聯的宣告的參數名稱。
例如,放置在實作宣告上的文件批註中的paramref
,會使用參數名稱來參考實作宣告的參數符號。
這可能會造成混淆,因為元數據簽章會使用定義宣告中的參數名稱。
建議您確保參數名稱在局部成員的宣告中保持一致,以避免這種混淆。
未解決的問題
成員類型
我們想要部分事件、建構函式、運算元、欄位嗎? 我們建議前兩個成員類型,但可以考慮任何其他子集。
您也可以考慮部分 主要 建構函式,例如,允許使用者在多個部分類型宣告上擁有相同的參數清單。
屬性位置
我們應該辨識 [method:]
屬性目標規範,適用於部分事件(或者僅限於定義宣告)嗎?
然後,所生成的存取子屬性會是來自定義(或僅是定義)宣告部分的 method
目標屬性,及來自執行宣告存取子的自我目標屬性和 method
目標屬性的串接。
合併不同宣告類型的屬性是史無前例的,事實上,Roslyn 中目前屬性比對的實作並不支援這一點。
我們也可以考慮辨識 [param:]
和 [return:]
,而不只是在部分事件上,而是所有類似字段的事件和 extern 事件。
如需詳細資訊,請參閱 https://github.com/dotnet/roslyn/issues/77254。