パラメーターなしの構造体コンストラクター
手記
この記事は機能仕様です。 仕様は、機能の設計ドキュメントとして機能します。 これには、提案された仕様の変更と、機能の設計と開発時に必要な情報が含まれます。 これらの記事は、提案された仕様の変更が最終決定され、現在の ECMA 仕様に組み込まれるまで公開されます。
機能の仕様と完成した実装の間には、いくつかの違いがある可能性があります。 これらの違いは、関連する 言語設計会議 (LDM) ノートでキャプチャされます。
機能仕様を C# 言語標準に導入するプロセスの詳細については、仕様に関する記事を参照してください。
チャンピオンの課題: https://github.com/dotnet/csharplang/issues/99
概要
構造体型のパラメーターなしのコンストラクターとインスタンス フィールド初期化子をサポートします。
モチベーション
明示的なパラメーターなしのコンストラクターでは、構造体型の最小構築インスタンスをより詳細に制御できます。
インスタンス フィールド初期化子を使用すると、複数のコンストラクター間での初期化が簡略化されます。
これらを組み合わせることで、struct
宣言と class
宣言の間に明らかなギャップが埋められます。
フィールド初期化子のサポートにより、プライマリ コンストラクターを明示的に実装せずに、record struct
宣言内のフィールドを初期化することもできます。
record struct Person(string Name)
{
public object Id { get; init; } = GetNextId();
}
パラメーターを持つコンストラクターで構造体フィールド初期化子がサポートされている場合は、パラメーターなしのコンストラクターにも拡張するのが自然なようです。
record struct Person()
{
public string Name { get; init; }
public object Id { get; init; } = GetNextId();
}
建議
インスタンス フィールド初期化子
構造体のインスタンス フィールド宣言には初期化子を含めることができます。
クラスフィールド初期化子と同様に、§15.5.6.3:
インスタンス フィールドの変数初期化子は、作成されるインスタンスを参照できません。
構造体にフィールド初期化子があり、宣言されたインスタンス コンストラクターがない場合、フィールド初期化子は実行されないため、エラーが報告されます。
struct S { int F = 42; } // error: 'struct' with field initializers must include an explicitly declared constructor
コンストラクター
構造体は、パラメーターなしのインスタンス コンストラクターを宣言できます。
パラメーターなしのインスタンス コンストラクターは、struct
、readonly struct
、ref struct
、record struct
など、すべての構造体の種類に対して有効です。
パラメーターなしのインスタンス コンストラクターが宣言されていない場合、構造体 (§16.4.9を参照) ...
すべての値型フィールドを既定値に設定し、すべての参照型フィールドを null に設定した結果の値を常に返すパラメーターなしのインスタンス コンストラクターが暗黙的に存在します。
修飾子
パラメーターなしのインスタンス構造体コンストラクターは、public
宣言する必要があります。
struct S0 { } // ok
struct S1 { public S1() { } } // ok
struct S2 { internal S2() { } } // error: parameterless constructor must be 'public'
メタデータから型をインポートする場合、パブリックでないコンストラクターは無視されます。
コンストラクターは、extern
または unsafe
宣言できます。
コンストラクターを partial
にすることはできません。
フィールド初期化子の実行
インスタンス変数初期化子 (§15.11.3) は次のように変更されます。
クラス のインスタンスコンストラクター がコンストラクター初期化子を持たない場合、またはその初期化子がフォーム
base(...)
の場合、そのコンストラクターは、そのクラスで宣言されたインスタンスフィールドのために指定された variable_initializerによる初期化を暗黙的に実行します。 これは、コンストラクターへのエントリの直後、および直接基底クラス コンストラクターの暗黙的な呼び出しの前に実行される一連の割り当てに対応します。構造体インスタンス コンストラクターにコンストラクター初期化子がない場合、そのコンストラクターは、その構造体で宣言されたインスタンス フィールドの variable_initializerで指定された初期化を暗黙的に実行します。 これは、コンストラクターへのエントリの直後に実行される割り当てのシーケンスに対応します。
構造体インスタンス コンストラクターに、既定のパラメーターなしのコンストラクター を表す
this()
コンストラクター初期化子がある場合、宣言されたコンストラクターは暗黙的にすべてのインスタンス フィールドをクリアし、その構造体で宣言されたインスタンス フィールドの variable_initializerで指定された初期化を実行します。 コンストラクターに入るとすぐに、すべての値型フィールドが既定値に設定され、すべての参照型フィールドがnull
に設定されます。 その直後に、variable_initializerに対応する一連の割り当てが実行されます。
明確な割り当て
インスタンス フィールド (fixed
フィールド以外) は、this()
初期化子を持たない構造体インスタンス コンストラクターで確実に割り当てる必要があります (§16.4.9を参照)。
struct S0 // ok
{
int x;
object y;
}
struct S1 // error: 'struct' with field initializers must include an explicitly declared constructor
{
int x = 1;
object y;
}
struct S2
{
int x = 1;
object y;
public S2() { } // error in C# 10 (valid starting in C# 11): field 'y' must be assigned
}
struct S3 // ok
{
int x = 1;
object y;
public S3() { y = 2; }
}
base()
初期化子がありません
base()
初期化子は、構造体コンストラクターでは許可されません。
コンパイラは、構造体インスタンス コンストラクターから基底 System.ValueType
コンストラクターの呼び出しを出力しません。
record struct
record struct
にフィールド初期化子があり、プライマリ コンストラクターもインスタンス コンストラクターも含まれていない場合、フィールド初期化子は実行されないため、エラーが報告されます。
record struct R0; // ok
record struct R1 { int F = 42; } // error: 'struct' with field initializers must include an explicitly declared constructor
record struct R2() { int F = 42; } // ok
record struct R3(int F); // ok
空のパラメーター リストを持つ record struct
には、パラメーターなしのプライマリ コンストラクターがあります。
record struct R3(); // primary .ctor: public R3() { }
record struct R4() { int F = 42; } // primary .ctor: public R4() { F = 42; }
record struct
の明示的なパラメーターなしのコンストラクターには、プライマリ コンストラクターまたは明示的に宣言されたコンストラクターを呼び出す this
初期化子が必要です。
record struct R5(int F)
{
public R5() { } // error: must have 'this' initializer that calls explicit .ctor
public R5(object o) : this() { } // ok
public int F = F;
}
田畑
暗黙的に定義されたパラメーターなしのコンストラクターは、フィールド型のパラメーターなしのコンストラクターを呼び出すのではなく、フィールドをゼロにします。 フィールド コンストラクターが無視されるという警告は報告されません。 C#9 からの変更はありません。
struct S0
{
public S0() { }
}
struct S1
{
S0 F; // S0 constructor ignored
}
struct S<T> where T : struct
{
T F; // constructor (if any) ignored
}
default
式
default
パラメーターなしのコンストラクターを無視し、ゼロインスタンスを生成します。
C#9 からの変更はありません。
// struct S { public S() { } }
_ = default(S); // constructor ignored, no warning
new()
オブジェクトの作成では、パラメーターなしのコンストラクター (public の場合) が呼び出されます。それ以外の場合、インスタンスはゼロになります。 C#9 からの変更はありません。
// public struct PublicConstructor { public PublicConstructor() { } }
// public struct PrivateConstructor { private PrivateConstructor() { } }
_ = new PublicConstructor(); // call PublicConstructor::.ctor()
_ = new PrivateConstructor(); // initobj PrivateConstructor
警告ウェーブは、コンストラクターを持ち、パラメーターなしのコンストラクターを持たない構造体型で new()
を使用するための警告を報告する場合があります。
型パラメーターにこのような構造体型を new()
制約または struct
制約で置き換える場合、警告は報告されません。
struct S { public S(int i) { } }
static T CreateNew<T>() where T : new() => new T();
_ = new S(); // no warning called
_ = CreateNew<S>(); // ok
初期化されていない値
明示的に初期化されていない構造体型のローカルまたはフィールドはゼロになります。 コンパイラは、空ではない初期化されていない構造体に対して明確な割り当てエラーを報告します。 C#9 からの変更はありません。
NoConstructor s1;
PublicConstructor s2;
s1.ToString(); // error: use of unassigned local (unless type is empty)
s2.ToString(); // error: use of unassigned local (unless type is empty)
配列の割り当て
配列の割り当ては、パラメーターなしのコンストラクターを無視し、ゼロ要素を生成します。 C#9 からの変更はありません。
// struct S { public S() { } }
var a = new S[1]; // constructor ignored, no warning
パラメーターの既定値 new()
パラメーターの既定値 new()
パブリックの場合はパラメーターなしのコンストラクターにバインドされます (値が定数ではないというエラーが報告されます)。それ以外の場合、インスタンスはゼロになります。
C#9 からの変更はありません。
// public struct PublicConstructor { public PublicConstructor() { } }
// public struct PrivateConstructor { private PrivateConstructor() { } }
static void F1(PublicConstructor s1 = new()) { } // error: default value must be constant
static void F2(PrivateConstructor s2 = new()) { } // ok: initobj
型パラメーターの制約: new()
と struct
new()
および struct
型パラメーター制約では、定義されている場合はパラメーターなしのコンストラクターを public
する必要があります (「制約の満たす 」を参照してください - §8.4.5 )。
コンパイラは、すべての構造体が new()
制約と struct
制約を満たしていると見なします。
C#9 からの変更はありません。
// public struct PublicConstructor { public PublicConstructor() { } }
// public struct InternalConstructor { internal InternalConstructor() { } }
static T CreateNew<T>() where T : new() => new T();
static T CreateStruct<T>() where T : struct => new T();
_ = CreateNew<PublicConstructor>(); // ok
_ = CreateStruct<PublicConstructor>(); // ok
_ = CreateNew<InternalConstructor>(); // compiles; may fail at runtime
_ = CreateStruct<InternalConstructor>(); // compiles; may fail at runtime
new T()
は System.Activator.CreateInstance<T>()
の呼び出しとして出力され、コンパイラは、CreateInstance<T>()
の実装が定義されている場合、public
パラメーターなしのコンストラクターを呼び出すと想定します。
.NET Framework では、制約が where T : new()
されている場合、Activator.CreateInstance<T>()
はパラメーターなしのコンストラクターを呼び出しますが、制約が where T : struct
場合はパラメーターなしのコンストラクターを無視するように見えます。
省略可能なパラメーター
省略可能なパラメーターを持つコンストラクターは、パラメーターなしのコンストラクターとは見なされません。 C#9 からの変更はありません。
struct S1 { public S1(string s = "") { } }
struct S2 { public S2(params object[] args) { } }
_ = new S1(); // ok: ignores constructor
_ = new S2(); // ok: ignores constructor
メタデータ
明示的なパラメーターなしの構造体インスタンス コンストラクターがメタデータに出力されます。
パブリック パラメーターなしの構造体インスタンス コンストラクターは、メタデータからインポートされます。非パブリック構造体インスタンス コンストラクターは無視されます。 C#9 からの変更はありません。
関連項目
設計会議
- https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-04-28.md#open-questions-in-record-and-parameterless-structs
- https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-03-10.md#parameterless-struct-constructors
- https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-01-27.md#field-initializers
C# feature specifications