無參數的結構建構函式
注意
本文是功能規格。 規格可作為功能的設計檔。 其中包含建議的規格變更,以及功能設計和開發期間所需的資訊。 這些文章會發佈,直到提議的規格變更完成並併併入目前的ECMA規格為止。
功能規格與已完成實作之間可能有一些差異。 這些差異是在相關 語言設計會議(LDM)記錄中擷取的。
總結
支援結構類型的無參數建構函式和實例字段初始化表達式。
動機
明確無參數建構函式可更充分掌控結構類型的最小建構實例。
實例欄位初始值設置可簡化在多個建構函式中的初始化過程。
一起,這些措施將填補 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()
如果公用,物件建立會叫用無參數建構函式;否則,實例為零。 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