共用方式為


擴充局部方法

注意

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

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

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

總結

此提案旨在消除 C# 中 partial 方法簽名的所有限制。 目標是擴充這些方法可與來源產生器搭配運作的案例集,以及成為 C# 方法的更一般宣告形式。

另請參閱原始的部分方法規格 (\15.6.9)。

動機

C# 對開發人員將方法分割成宣告和定義/實作的支援有限。

partial class C
{
    // The declaration of C.M
    partial void M(string message);
}

partial class C
{
    // The definition of C.M
    partial void M(string message) => Console.WriteLine(message);
}

partial 方法的其中一種行為是,當定義不存在時,語言只會清除對 partial 方法的任何呼叫。 基本上,它的行為就像呼叫 [Conditional] 方法,其中的條件被評估為假。

partial class D
{
    partial void M(string message);

    void Example()
    {
        M(GetIt()); // Call to M and GetIt erased at compile time
    }

    string GetIt() => "Hello World";
}

這項功能的原始動機是以設計工具產生的程式代碼形式產生來源。 用戶不斷編輯產生的程式代碼,因為他們想要連結所產生程式代碼的某些層面。 在元件初始化後,最值得注意的是 Windows Forms 啟動流程的部分。

編輯產生的程式代碼很容易出錯,因為任何導致設計工具重新產生程式碼的動作都會導致使用者編輯被清除。 partial 方法特性緩解了這種緊張,因為它允許設計師以 partial 方法的形式提供鉤子。

設計工具可以發出像 partial void OnComponentInit() 這樣的鉤子,而開發人員可以為其定義宣告,或不為其定義。 無論如何,產生的程式碼都可以編譯,而有興趣了解過程的開發人員可以根據需要進行整合。

這表示部分方法有數個限制:

  1. 必須有 void 傳回型別。
  2. 不能有 out 參數。
  3. 不能有任何無障礙功能(隱含 private)。

這些限制存在,因為程式語言必須在清除呼叫端時生成代碼。 由於成員無法在組件元數據中公開,因此只有 private 是唯一可能的存取方式。 這些限制也可用來限制可套用 partial 方法的案例集。

此處的建議是移除 partial 方法周圍的所有現有限制。 基本上,讓他們擁有 out 參數、非 void 傳回型別或任何類型的輔助功能。 如此的 partial 宣告之後會有一項額外的要求,即必須存在一個定義。 這表示語言不必考慮清除通話網站的影響。

這將擴展 partial 方法可以參與的生成器場景集,從而與我們的來源產生器特性緊密連結。 例如,可以使用下列模式來定義 regex:

[RegexGenerated("(dog|cat|fish)")]
partial bool IsPetMatch(string input);

這可讓開發人員以簡單的宣告方式選擇產生器,並提供產生器一組非常簡單的宣告,以在原始程式碼中查看以驅動其產生的輸出。

將這與生成器在連接以下代碼段時的難度相比。

var regex = new RegularExpression("(dog|cat|fish)");
if (regex.IsMatch(someInput))
{

}

假設編譯器不允許產生器修改用於實作此模式的程式碼,那麼產生器要做到這一點幾乎是不可能的。 他們需要在 IsMatch 實作中求助於反射,或要求使用者將其呼叫端變更為新方法,並重構 regex,以字串常值作為參數傳遞。 這很混亂。

詳細設計

語言將更改為允許使用明確的存取修飾詞來標註 partial 方法。 這表示它們可以標示為 privatepublic等...

partial 方法具有明確的存取性修飾詞時,即使存取性是 private,語言仍然要求宣告須有相符的定義:

partial class C
{
    // Okay because no definition is required here
    partial void M1();

    // Okay because M2 has a definition
    private partial void M2();

    // Error: partial method M3 must have a definition
    private partial void M3();
}

partial class C
{
    private partial void M2() { }
}

此外,語言將不再限制具有明確存取權限的 partial 方法中的內容。 這類宣告可以包含非 void 傳回型別、out 參數、extern 修飾詞等...這些簽章將具有 C# 語言的完整表達性。

partial class D
{
    // Okay
    internal partial bool TryParse(string s, out int i); 
}

partial class D
{
    internal partial bool TryParse(string s, out int i) { ... }
}

這明確允許 partial 方法參與 overridesinterface 實作:

interface IStudent
{
    string GetName();
}

partial class C : IStudent
{
    public virtual partial string GetName(); 
}

partial class C
{
    public virtual partial string GetName() => "Jarde";
}

partial 方法包含非法元素時,編譯程式會變更它發出的錯誤,基本上說:

不能在缺少顯式存取性的 partial 方法上使用 ref

這可協助開發人員在使用這項功能時,以正確的方向前進。

限制:

  • 具有明確存取範圍的 partial 宣告必須具有定義
  • partial 宣告和定義簽名必須與所有方法和參數修飾詞一致。 唯一可能不同的層面是參數名稱和屬性清單(這不是新的,而是 partial 方法的現有需求)。

提問

對所有成員產生偏愛

考慮到我們正在擴充 partial,使其更適合程式碼產生器,我們是否也應該將其擴充到支援所有類別成員? 例如,我們應該能夠宣告 partial 建構函式、運算符等...

決議 這個想法是合理的,但是在 C# 9 的排程中,我們正在嘗試避免不必要的功能擴張。 想要解決立即擴充功能以適應現代來源產生器的問題。

針對 C# 10 版本,將考慮擴充 partial 以支援其他成員。 我們可能會考慮這個擴展。 這仍然是一個積極的建議,但它尚未實施。

使用抽象而非部分

此提案的關鍵在於確保宣告具有對應的定義/實作。 假設我們應該使用 abstract,因為它已經是一個語言關鍵詞,這會迫使開發人員去思考如何實作。

決議 對此進行了充分討論,但最終否決。 是,需求很熟悉,但概念明顯不同。 可以輕易地使開發人員誤以為他們正在建立虛擬插槽,而其實並非如此。