次の方法で共有


partial プロパティ

手記

この記事は機能仕様です。 仕様は、機能の設計ドキュメントとして機能します。 これには、提案された仕様の変更と、機能の設計と開発時に必要な情報が含まれます。 これらの記事は、提案された仕様の変更が最終決定され、現在の ECMA 仕様に組み込まれるまで公開されます。

機能の仕様と完成した実装の間には、いくつかの違いがある可能性があります。 これらの違いは、関連する 言語設計会議 (LDM) ノートでキャプチャされます。

機能仕様を C# 言語標準に導入するプロセスの詳細については、仕様に関する記事を参照してください。

チャンピオンの課題: https://github.com/dotnet/csharplang/issues/6420

文法

property_declaration の文法 (§14.7.1) は以下のように更新されます。

property_declaration
-    : attributes? property_modifier* type member_name property_body
+    : attributes? property_modifier* 'partial'? type member_name property_body
    ;  

解説: これは、method_header(§15.6.1)class_declaration(§15.2.1) を指定する方法と多少似ています。 (問題 #946 は順序付けの要件を緩和することを提案しており、おそらく partial 修飾子を許可するすべての宣言に適用されることに注意してください。近い将来、このような順序の緩和を指定し、この機能が実装されているのと同じリリースで実装する予定です)。

宣言の定義と実装

プロパティ宣言に 部分 修飾子が含まれている場合、そのプロパティは 部分プロパティと言われます。 部分プロパティは、部分型のメンバーとしてのみ宣言できます。

部分プロパティ宣言は、アクセサーにすべてセミコロン本体があり、extern 修飾子がない場合の定義宣言であると言われます。 それ以外の場合は、宣言 を実装するです。

partial class C
{
    // Defining declaration
    public partial string Prop { get; set; }

    // Implementing declaration
    public partial string Prop { get => field; set => field = value; }
}

定義宣言のセミコロン アクセサー本体で構文形式を予約しているため、部分プロパティを自動的に実装することはできません。 したがって、自動的に実装されるプロパティ (§15.7.4) を次のように調整します。

自動的に実装されるプロパティ (または短い場合は自動プロパティ) は、セミコロンのみのアクセサー本体を持つ非抽象、非 extern、非部分、 非 ref 値プロパティです。

注釈。 コンパイラは、1 つの宣言を分離して見て、それが定義宣言か実装宣言かを知ることができるのに役立ちます。 したがって、たとえば、2 つの同一の partial プロパティ宣言を含めることで、自動プロパティを許可しないようにします。 この機能のユース ケースでは、自動プロパティを使用して部分プロパティを実装する必要はないと考えられますが、簡単な実装が必要な場合は、field キーワードを使用すると、十分に単純であると考えられます。


部分プロパティには、宣言 を定義する と、宣言 を実装するが 1 つ必要です。

注釈。 また、たとえば、異なるアクセサーを異なる場所に実装できるように、宣言を複数の部分に分割することは役に立たないと考えています。 したがって、我々は単に部分的な方法によって確立されたスキームを模倣します。


部分メソッドの定義宣言のみがオーバーロードの解決にどのように関与するかと同様に、部分プロパティの定義宣言のみが参照に参加します。

注釈。 コンパイラでは、定義宣言のシンボルのみがメンバー リストに表示され、実装パーツのシンボルには定義シンボルを介してアクセスできることを想定しています。 ただし、null 許容分析などの一部の機能がより有用な動作を提供するために、実装宣言まで透過することがあります。

partial class C
{
    public partial string Prop { get; set; }
    public partial string Prop { get => field; set => field = value; }

    public C() // warning CS8618: Non-nullable property 'Prop' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
    {
    }
}

部分プロパティは、abstract 修飾子を持つことは許可されていません。

部分プロパティは、インターフェイス プロパティを明示的に実装できません。

属性のマージ

部分メソッドと同様に、結果のプロパティの属性は、パーツの結合された属性が未指定の順序で連結され、重複は削除されません。

呼び出し元情報属性

標準から次の言語を調整します。

部分メソッド の定義部分と実装部分のどちらのパラメーターにも同じ呼び出し元情報属性があると、メンバー 宣言でエラーになります。 定義パーツ内の呼び出し元情報属性のみが適用されますが、実装パーツでのみ発生する呼び出し元情報属性は無視されます。

  • 記述されたエラーは、AllowMultiple = trueを持たないこれらの属性の定義から除外されます。 複数の部分宣言を含め、それらを複数回使用すると、エラーが発生します。
  • 部分メソッドの実装部分のパラメーターに呼び出し元情報属性が適用されると、Roslyn コンパイラは警告を報告します。 また、部分プロパティ内の同じシナリオに対する警告も報告されます。

一致する署名

2020年9月14日 LDM会議では 警告波で導入された部分方式の署名マッチングに対する一連の「厳格な」要件を定義しました。 部分プロパティには、不一致に関するすべての診断が既定で報告され、警告ウェーブの背後に保持されない点を除き、可能な限りシグネチャ一致の部分メソッドに似た要件があります。

署名の一致要件は次のとおりです。

  1. 型と ref の種類の違いは、ランタイムにとって重要な部分プロパティ宣言の場合、コンパイル時エラーが発生します。
  2. 部分プロパティ宣言内のタプル要素名の違いにより、部分メソッドの場合と同じコンパイル時エラーが発生します。
  3. プロパティ宣言とそのアクセサー宣言は同じ修飾子を持つ必要がありますが、修飾子は異なる順序で表示される場合があります。
    • 例外: これは、宣言 を実装するにのみ表示される可能性がある extern 修飾子には適用されません。
  4. 部分プロパティ宣言のシグネチャに構文上の違いがある場合、コンパイル時の警告が発生しますが、次の例外があります。
    • プロパティ宣言の一部または内部の属性リストは、一致する必要はありません。 代わりに、属性マージごとに、対応する位置の属性のマージが実行されます。
    • null 許容コンテキストの違いによって警告が発生することはありません。 言い換えると、型の 1 つが null 許容未指定で、もう一方の型が null 許容注釈付きまたは null 許容注釈付きでない場合の違いでは、警告は発生しません。
    • 既定のパラメーター値は一致する必要はありません。 部分インデクサーの実装部分に既定のパラメーター値がある場合、警告が報告されます。 これは、部分メソッドの実装部分に既定のパラメーター値がある場合に発生する既存の警告に似ています。
  5. 宣言の定義と実装によってパラメーター名が異なる場合、警告が発生します。 定義部分のパラメーター名は、使用サイトと出力で使用されます。
  6. null 許容の違いは null 許容未指定を伴わないため、警告が発生します。 アクセサー本体を分析する場合は、実装パーツのシグネチャが使用されます。 定義パーツ署名は、使用サイトの分析時および出力時に使用されます。 これは部分メソッドと一致します。
partial class C1
{
    public partial string Prop { get; private set; }

    // Error: accessor modifier mismatch in 'set' accessor of 'Prop'
    public partial string Prop { get => field; set => field = value; }
}

partial class C2
{
    public partial string Prop { get; init; }

    // Error: implementation of 'Prop' must have an 'init' accessor to match definition
    public partial string Prop { get => field; set => field = value; }
}

partial class C3
{
    public partial string Prop { get; }

    // Error: implementation of 'Prop' cannot have a 'set' accessor because the definition does not have a 'set' accessor.
    public partial string Prop { get => field; set => field = value; }
}

partial class C4
{
    public partial string this[string s = "a"] { get; set; }
    public partial string this[string s] { get => s; set { } } // ok

    public partial string this[int i, string s = "a"] { get; set; }
    public partial string this[int i, string s = "a"] { get => s; set { } } // CS1066: The default value specified for parameter 's' will have no effect because it applies to a member that is used in contexts that do not allow optional arguments
}

ドキュメントのコメント

部分プロパティに対するドキュメントコメントの動作を部分メソッドの挙動と一致させたいと考えています。 その動作については、https://github.com/dotnet/csharplang/issues/5193で詳しく説明します。

部分プロパティの定義または実装部分にドキュメント コメントを含めることができます。 (ドキュメント コメントはプロパティ アクセサーではサポートされていません。

ドキュメント コメントがプロパティの 1 つの部分にのみ存在する場合、それらのドキュメント コメントは通常どおりに使用されます (ISymbol.GetDocumentationCommentXml()で表示され、ドキュメント XML ファイルに書き出されます)。

両方の部分にドキュメント コメントが存在する場合、定義パーツのすべてのドキュメント コメントが削除され、実装パーツのドキュメント コメントのみが使用されます。

たとえば、次のプログラムです。

/// <summary>
/// My type
/// </summary>
partial class C
{
    /// <summary>Definition part comment</summary>
    /// <returns>Return value comment</returns>
    public partial int Prop { get; set; }
    
    /// <summary>Implementation part comment</summary>
    public partial int Prop { get => 1; set { } }
}

結果は次の XML ドキュメント ファイルになります。

<?xml version="1.0"?>
<doc>
    <assembly>
        <name>ConsoleApp1</name>
    </assembly>
    <members>
        <member name="T:C">
            <summary>
            My type
            </summary>
        </member>
        <member name="P:C.Prop">
            <summary>
            Implementation part comment
            </summary>
        </member>
    </members>
</doc>

部分宣言間でパラメーター名が異なる場合、<paramref> 要素は、ソース コードのドキュメント コメントに関連付けられている宣言のパラメーター名を使用します。 たとえば、実装宣言に配置されたドキュメント コメントの paramref は、そのパラメーター名を使用して実装宣言のパラメーター シンボルを参照します。 これは部分メソッドと一致します。

/// <summary>
/// My type
/// </summary>
partial class C
{
    public partial int this[int x] { get; set; }

    /// <summary>
    /// <paramref name="x"/> // warning CS1734: XML comment on 'C.this[int]' has a paramref tag for 'x', but there is no parameter by that name
    /// <paramref name="y"/> // ok. 'Go To Definition' will go to 'int y'.
    /// </summary>
    public partial int this[int y] { get => 1; set { } } // warning CS9256: Partial property declarations 'int C.this[int x]' and 'int C.this[int y]' have signature differences.
}

結果は次の XML ドキュメント ファイルになります。

<?xml version="1.0"?>
<doc>
    <assembly>
        <name>ConsoleApp1</name>
    </assembly>
    <members>
        <member name="T:C">
            <summary>
            My type
            </summary>
        </member>
        <member name="P:C.Item(System.Int32)">
            <summary>
            <paramref name="x"/> // warning CS1734: XML comment on 'C.this[int]' has a paramref tag for 'x', but there is no parameter by that name
            <paramref name="y"/> // ok. 'Go To Definition' will go to 'int y'.
            </summary>
        </member>
    </members>
</doc>

メタデータ署名では定義部分のパラメーター名が使用されるため、混乱を招く可能性があります。 この混乱を避けるために、パラメーター名がパーツ間で一致することを確認することをお勧めします。

インデクサー

2022 年 11 月 2 日の LDM 会議に基づき、この機能でインデクサーがサポートされるようになります。

インデクサーの文法は次のように変更されます。

indexer_declaration
-    : attributes? indexer_modifier* indexer_declarator indexer_body
+    : attributes? indexer_modifier* 'partial'? indexer_declarator indexer_body
-    | attributes? indexer_modifier* ref_kind indexer_declarator ref_indexer_body
+    | attributes? indexer_modifier* 'partial'? ref_kind indexer_declarator ref_indexer_body
    ;

部分的なインデクサー パラメーターは、一致するシグネチャと同じ規則に従って、宣言間で一致する必要があります。 属性のマージ は、部分的なインデクサー パラメーター間で実行されます。

partial class C
{
    public partial int this[int x] { get; set; }
    public partial int this[int x]
    {
        get => this._store[x];
        set => this._store[x] = value;
    }
}

// attribute merging
partial class C
{
    public partial int this[[Attr1] int x]
    {
        [Attr2] get;
        set;
    }

    public partial int this[[Attr3] int x]
    {
        get => this._store[x];
        [Attr4] set => this._store[x] = value;
    }

    // results in a merged member emitted to metadata:
    public int this[[Attr1, Attr3] int x]
    {
        [Attr2] get => this._store[x];
        [Attr4] set => this._store[x] = value;
    }
}

未解決の問題

その他のメンバーの種類

コミュニティメンバーがの部分的なイベントのサポートを求めてディスカッションを始めました。 2022 年 11 月 2 日の LDM 会議では,当時誰も求めていなかったため、イベントのサポートについて話し合うことにしました。 この要求が入り、最後に話し合ってから 1 年以上経ったので、この質問をもう一度見直したいと思うかもしれません。

コンストラクター、演算子、フィールドなどの部分的な宣言を許可することもできますが、既に部分プロパティを実行しているからといって、これらの設計上の負担が正当化されるかどうかは不明です。