无参数结构构造函数

注意

本文是特性规范。 该规范充当该功能的设计文档。 它包括建议的规范更改,以及功能设计和开发过程中所需的信息。 这些文章将发布,直到建议的规范更改最终确定并合并到当前的 ECMA 规范中。

功能规范与已完成的实现之间可能存在一些差异。 这些差异记录在相关的语言设计会议 (LDM) 说明中。

可以在 规范一文中详细了解将功能规范采用 C# 语言标准的过程。

总结

支持结构类型的无参数构造函数和实例字段初始化器。

动机

显式无参数构造函数可更好地控制结构类型的最小构造实例。 实例字段初始值设定项可以在多个构造函数中简化初始化过程。 它们共同将缩小 structclass 声明之间的明显差距。

对字段初始值设定项的支持还允许在 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

构造函数

结构可以声明无参数实例构造函数。

无参数实例构造函数适用于所有结构类型,包括 structreadonly structref structrecord struct

如果未声明无参数实例构造函数,则结构(请参阅 §16.4.9)...

隐式具有无参数实例构造函数,该构造函数始终返回将所有值类型字段设置为默认值的值,并将所有引用类型字段设置为 null。

修饰符

无参数实例结构构造函数必须声明为 public

struct S0 { }                   // ok
struct S1 { public S1() { } }   // ok
struct S2 { internal S2() { } } // error: parameterless constructor must be 'public'

从元数据导入类型时,将忽略非公共构造函数。

构造函数可以声明为 externunsafe。 构造函数不能是 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 比较没有变化。

另请参阅

设计会议