Start Debugging

解決: C# の CS9035「Required member 'X' must be set in the object initializer」

CS9035 は required とマークされたメンバーが割り当てられていないことを意味します。オブジェクト初期化子で割り当てるか、すべての required メンバーを設定する [SetsRequiredMembers] 付きのコンストラクターを追加します。

CS9035 は、required メンバーを持つ型のインスタンスを作成しながら、そのメンバーを割り当てていないときにコンパイル時に発生します。コンパイラーは、オブジェクトが構築を離れる前にすべての required フィールドまたはプロパティが設定されることを求めます。直接的な方法で修正しましょう。たとえば new Person { Name = "Ada" } のように、そのメンバーをオブジェクト初期化子に追加します。コンストラクターがすでにそのメンバーを割り当てている場合は、そのコンストラクターに [SetsRequiredMembers] を付けて、初期化子を要求しないようコンパイラーに伝えます。これは .NET 11 上の C# 14 で検証していますが、required 修飾子とこの診断は .NET 7 上の C# 11 以来同じように動作します。

コンテキストの中でのエラー

完全なメッセージは、コンパイラーに欠けている正確なメンバーを名指しします。

error CS9035: Required member 'Person.Name' must be set in the object initializer or attribute constructor.

割り当てられていない required メンバーごとに CS9035 が 1 つ表示されるため、required プロパティが 3 つある型で空の new Person() を書くと、一度に 3 つのエラーが出ます。これはコンパイル時の診断であり、ランタイムの例外ではありません。ビルドが失敗し、何も実行されません。それが required の目的そのものです。チェックは、本番で遭遇する NullReferenceException から、エディターで見える赤い波線へと移りました。

なぜこれが起こるのか

C# 11 で導入された required 修飾子は、フィールドまたはプロパティを初期化時に必須としてマークします。任意の式が型の新しいインスタンスを構築するとき、コンパイラーは、オブジェクト初期化子を通じてか、それらを設定すると約束するコンストラクターを通じてか、すべての required メンバーが割り当てられていることを検証します。それを証明できない場合、CS9035 を発行します。

鍵となる言葉は「証明する」です。コンパイラーはあなたのコードを実行しません。コンパイラーが信頼するのは 2 つだけです。オブジェクト初期化子で直接割り当てるメンバーと、[SetsRequiredMembers] で明示的に注釈されたコンストラクターです。本体でたまたまそのメンバーを割り当てているが属性を欠くコンストラクターは、まったくカウントされません。コンパイラーはそれを突き止めるために本体を読みはしません。だからこそ、コンストラクターが明らかに値を設定していても、エラーは残るのです。コンパイラーに見せるのではなく、伝えなければなりません。

3 つの状況がエラーを生みます。

最小限の再現

CS9035 を引き起こす最小の型です。

// .NET 11, C# 14
public class Person
{
    public required string Name { get; init; }
    public required string Email { get; init; }
    public int Age { get; init; }   // not required, optional
}

これらの構築のいずれもコンパイルに失敗します。

// .NET 11, C# 14
var a = new Person();                        // CS9035 x2 (Name and Email)
var b = new Person { Name = "Ada" };         // CS9035 x1 (Email still missing)
var c = new Person { Age = 36 };             // CS9035 x2 (Name and Email)

両方の required メンバーが存在するときにのみコンパイルされます。

// .NET 11, C# 14 -- compiles
var ok = new Person { Name = "Ada", Email = "ada@example.com" };

Age が省略されていて、それで問題ないことに注目してください。required はメンバーごとです。オプションのメンバーはオプションのままです。コンパイラーが気にするのは、required 修飾子を持つメンバーが割り当てられていることだけです。

詳細な修正方法

これらの選択肢を順番に進めてください。最初のものが大半のケースの答えです。残りは、初期化子が求めるものではない状況をカバーします。

1. オブジェクト初期化子で required メンバーを割り当てる

意図された修正は、呼び出し側ですべての required メンバーを設定することです。

// .NET 11, C# 14
var person = new Person
{
    Name = "Ada Lovelace",
    Email = "ada@example.com",
    // Age is optional, omit it freely
};

これが required の目的です。型の契約はこれらのフィールドが必須だと述べ、初期化子はそれを守ります。同じ値を繰り返していることに気づいたら、それは型がコンストラクターを求めているという合図です。それが次の修正です。

2. [SetsRequiredMembers] で注釈したコンストラクターを追加する

コンストラクターがすでに値を受け取って割り当てているなら、System.Diagnostics.CodeAnalysis.SetsRequiredMembers で装飾します。この属性は、コンストラクターがすべての required メンバーを初期化するとコンパイラーに保証するため、呼び出し側はもはやオブジェクト初期化子を必要としません。

// .NET 11, C# 14
using System.Diagnostics.CodeAnalysis;

public class Person
{
    public required string Name { get; init; }
    public required string Email { get; init; }
    public int Age { get; init; }

    [SetsRequiredMembers]
    public Person(string name, string email)
    {
        Name = name;
        Email = email;
    }
}

// now this compiles, no initializer needed
var person = new Person("Ada", "ada@example.com");

1 つの鋭い落とし穴があります。[SetsRequiredMembers] は表明であって、検証された保証ではありません。コンパイラーはあなたの言葉を信じ、コンストラクターが実際にすべての required メンバーを割り当てているかは検証しません。属性を追加したのに本体で Email の設定を忘れると、CS9035 も警告も出ず、値を約束した場所に null が入るだけです。属性を正直に保ってください。

注釈されたコンストラクターの隣にパラメーターなしのコンストラクターを残す場合、パラメーターなしのバージョンの呼び出し側は依然として初期化子を必要とします。属性は、それが乗っている特定のコンストラクターだけを免除します。

3. メンバーが実際には必須でないなら required を外す

メンバーに妥当な既定値があるか、本当にオプションであるなら、そもそも required であるべきではありません。修飾子を外すと義務がなくなります。

// .NET 11, C# 14
public class Person
{
    public required string Name { get; init; }
    public string Email { get; init; } = "";   // was required, now optional with a default
}

これが正しい選択であることは驚くほど多いです。妥当な既定値がなく、値なしでの構築がオブジェクトを無効な状態に置いてしまう場合にのみ required に手を伸ばしてください。プロパティに既定値を与えて required を外すほうが、すべての呼び出し箇所に空文字列を渡させるより清潔です。

4. record には位置指定または [SetsRequiredMembers] コンストラクターを使う

位置指定 record はパラメーターごとに init プロパティを生成しますが、それらは既定では required ではありません。プライマリコンストラクターを持つ record に required プロパティを明示的に追加すると、プライマリコンストラクターはそれを自動的には満たさず、位置指定形式を使うと CS9035 が出ます。コンストラクターがカウントされるべきに見えるので、これは人を混乱させます。その理由については dotnet/csharplang #6780 の設計に関する議論を参照してください。修正は、位置指定の呼び出しの上に初期化子でそのプロパティを設定するか、それを割り当てるコンストラクターに [SetsRequiredMembers] を追加することです。

// .NET 11, C# 14
using System.Diagnostics.CodeAnalysis;

public record Product(string Sku)
{
    public required string Name { get; init; }

    [SetsRequiredMembers]
    public Product(string sku, string name) : this(sku) => Name = name;
}

// both work now
var withInit = new Product("A-100") { Name = "Widget" };
var withCtor = new Product("A-100", "Widget");

record の構築全体を位置指定パラメーターを通じて強制したいなら、required プロパティより素の init プロパティを優先してください。位置指定 record と required メンバーを混ぜるのは混乱の元です。型がどちらのモデルを使うかを決めてください。record が価値を持つのはいつかというより広い見方については、C# の record vs class vs struct を参照してください。

落とし穴とバリエーション

一握りの状況が、メッセージからは明らかでない理由で CS9035 またはそれに似たものを生み出します。

心に留めておくべきメンタルモデルはこうです。required は「これを提供しなければならない」というルールを、ランタイムの願いからコンパイル時の保証へと移し、CS9035 はその保証が仕事をしている姿です。それを見たとき、修正は常に「呼び出し側で値を提供する」か「コンストラクターがすでにそれを提供しているとコンパイラーに伝える」のどちらかです。型にとってどちらが真かを決め、初期化子を埋めるか [SetsRequiredMembers] を追加してください。そして、メンバーを実際に設定せずにエラーを黙らせる手段として属性に手を伸ばさないでください。

関連

出典

Comments

Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.

< 戻る