rksoftware

Visual Studio とか C# とかが好きです

C# 11 の新機能を確認「required 修飾子」

C# 11 の新機能を確認しています。目次は次の記事です。
rksoftware.hatenablog.com

今回は 「 required 修飾子 」。公式 Learn の記事は次です。

クラスや構造体のメンバーを required キーワードで修飾すると必須にできます。必須とは初期化が必須ということで、null で初期化することは OK です。

■ 確認

次のコードは 2 行目のコードがエラーになります。

new Class1 { P1 = null, P2 = null };
new Class1 { P1 = null };  // エラー

class Class1
{
    internal string? P1 { get; init; }
    internal required string? P2 { get; init; }
}
エラー CS9035 必要なメンバー 'Class1.P2' は、オブジェクト初期化子または属性コンストラクターに設定する必要があります。

■ いろいろな使えないパターン

インターフェイスのプロパティ

インターフェイスのプロパティに付けるとエラーになります。次の P1P2 ともにエラーになります。

interface Interface1
{
    string? P0 { get; set; }
    required string? P1 { get => P0; set => P0 = value; }  // エラー
    required string? P2 { get; set; }  // エラー
}
エラー CS0106 修飾子 'required' がこの項目に対して有効ではありません
エラー CS0106 修飾子 'required' がこの項目に対して有効ではありません

インターフェイスの明示的な実装

インターフェイスの明示的な実行に付けるとエラーになります。次の Class3 がエラーになります。

interface Interface1 { string? P1 { get; set; } }
class Class1 : Interface1 { string? Interface1.P1 { get; set; } }
class Class2 : Interface1 { public required string? P1 { get; set; } }
class Class3 : Interface1 { required string? Interface1.P1 { get; set; } }  // エラー
エラー CS0106 修飾子 'required' がこの項目に対して有効ではありません
  • Class1 は required が付いていないのでエラーになっていません。
  • Class2 はインターフェイスを実装していますが、インターフェイスの明示的な実装ではないのでエラーになっていません。
  • Class3 はインターフェイスの明示的な実装に付いているのでエラーになっています。

クラスより小さいアクセス修飾子

クラス自体よりも小さなアクセス修飾子をもつプロパティに付けるとエラーになります。次の Class1AClass2AClass3A がエラーになります。

file class Class1A { private required string? P1 { get; set; } } // エラー
internal class Class2A { private required string? P1 { get; set; } } // エラー
public class Class3A { internal required string? P1 { get; set; } }  // エラー

file class Class1B { internal required string? P1 { get; set; } }
internal class Class2B { internal required string? P1 { get; set; } }
public class Class3b { public required string? P1 { get; set; } }
エラー CS9032 必要なメンバー 'Class1A.P1' の表示を減らす、また含まれる型の 'Class1A' より小さいセッターの表示を設定することはできません。
エラー CS9032 必要なメンバー 'Class2A.P1' の表示を減らす、また含まれる型の 'Class2A' より小さいセッターの表示を設定することはできません。
エラー CS9032 必要なメンバー 'Class3A.P1' の表示を減らす、また含まれる型の 'Class3A' より小さいセッターの表示を設定することはできません。

Class1BClass2BClass3B はプロパティのアクセス修飾子がクラス自体のアクセス修飾子と同じかより大きくなっているのでエラーになっていません。

new 制約

new 制約のあるジェネリック型パラメータの条件を満たしません。次の 2 行目のメソッド呼び出しがエラーになります。

Method1(new Class1());
Method1(new Class2 { P1 = null });  // エラー

static void Method1<T>(T arg) where T : new() { }

class Class1 { internal string P1 { get; init; } }
class Class2 { internal required string P1 { get; init; } }
エラー CS9040 'Class2'に必要なメンバーがあるため、'Class2' は、ジェネリック型またはメソッド 'Method1<T>(T)'のパラメーター 'T' の 'new()' 制約を満たすことができません。

必須プロパティがあると、new T(); ができないのでエラーということでしょう。

record 型の位置指定パラメータ

record 型の位置指定パラメータにはつけられません。次のコードはエラーになります。

record Record1B(string? P1, required string? P2);  // エラー
エラー CS1001 識別子がありません

明示的にプロパティを作ると、required が付けられますが、new が微妙に。何がしたいのか良くわからない感じに。このパターンで required を使うことはなさそうですね。

new Record2(null, null);  // エラー
new Record2(null, null) { P2 = "b" };
new Record2 { P2 = "b" };

record Record2()
{
    public Record2(string? p1, string? p2) : this() => P2 = p2;
    internal required string? P2 { get; init; }
}
エラー CS9035 必要なメンバー 'Record2.P2' は、オブジェクト初期化子または属性コンストラクターに設定する必要があります。

SetsRequiredMembers 属性で少し意味の分かる形になります。

new Record3(null, null);
new Record3();  // エラー
new Record3 { P2 = null };

record Record3()
{
    [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] public Record3(string? p1, string? p2) : this() => P2 = p2;
    internal required string? P2 { get; init; }
}
エラー CS9035 必要なメンバー 'Record3.P2' は、オブジェクト初期化子または属性コンストラクターに設定する必要があります。

ただこの SetsRequiredMembers 属性、必須プロパティはこのコンストラクタで設定されるとコンパイラに指示するだけで、実際に設定しているかは自分で漏れないよう頑張る必要があるそうです。次のコードは P2 プロパティが設定されていないのにエラーになりません。

Console.WriteLine(new Record4(null, null).P2 is null);  // True

record Record4()
{
    [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] public Record4(string? p1, string? p2) : this() { }
    internal required string? P2 { get; init; }
}

■ これはいいものですね

この必須プロパティを作る機能、とても良いです。実戦導入してます。使っていきましょう!