仅 Init 资源库Init Only Setters

总结Summary

此建议将仅 init 属性和索引器的概念添加到 c #。This proposal adds the concept of init only properties and indexers to C#. 这些属性和索引器可以在创建对象时设置,但 get 仅在对象创建完成后才会有效。These properties and indexers can be set at the point of object creation but become effectively get only once object creation has completed. 这允许在 c # 中使用更灵活的不可变模型。This allows for a much more flexible immutable model in C#.

动机Motivation

自1.0 以来,用 c # 生成不可变数据的基础机制并未更改。The underlying mechanisms for building immutable data in C# haven't changed since 1.0. 它们仍保留:They remain:

  1. 将字段声明为 readonlyDeclaring fields as readonly.
  2. 声明仅包含 get 访问器的属性。Declaring properties that contain only a get accessor.

这些机制在允许构造不可变的数据时有效,但它们通过向类型的样板代码添加成本并从对象和集合初始值设定项的功能中选择此类类型来实现此目的。These mechanisms are effective at allowing the construction of immutable data but they do so by adding cost to the boilerplate code of types and opting such types out of features like object and collection initializers. 这意味着开发人员必须在易用性和不可变性之间进行选择。This means developers must choose between ease of use and immutability.

像声明类型一样,简单的不可变对象(例如 Point )需要两个样板板代码,以支持构造。A simple immutable object like Point requires twice as much boiler plate code to support construction as it does to declare the type. 类型越大,此样板板式的成本就越大:The bigger the type the bigger the cost of this boiler plate:

struct Point
{
    public int X { get; }
    public int Y { get; }

    public Point(int x, int y)
    {
        this.X = x;
        this.Y = y;
    }
}

init通过允许调用方在构造操作过程中改变成员,访问器使不可变对象更具灵活性。The init accessor makes immutable objects more flexible by allowing the caller to mutate the members during the act of construction. 这意味着对象的不可变属性可以参与对象初始值设定项,因此不再需要类型中的所有构造函数样板。That means the object's immutable properties can participate in object initializers and thus removes the need for all constructor boilerplate in the type. Point类型现在只是:The Point type is now simply:

struct Point
{
    public int X { get; init; }
    public int Y { get; init; }
}

然后,使用者可以使用对象初始值设定项来创建对象。The consumer can then use object initializers to create the object

var p = new Point() { X = 42, Y = 13 };

详细设计Detailed Design

init 访问器init accessors

使用 init 访问器代替访问器来声明仅 init 属性 (或索引器) setAn init only property (or indexer) is declared by using the init accessor in place of the set accessor:

class Student
{
    public string FirstName { get; init; }
    public string LastName { get; init; }
}

init在以下情况下,包含访问器的实例属性被视为可设置,但在本地函数或 lambda 中除外:An instance property containing an init accessor is considered settable in the following circumstances, except when in a local function or lambda:

  • 在对象初始值设定项期间During an object initializer
  • with表达式初始值设定项期间During a with expression initializer
  • 在或上包含类型或派生类型的实例构造函数中 this``baseInside an instance constructor of the containing or derived type, on this or base
  • init在任何属性的访问器中、在 this 或上baseInside the init accessor of any property, on this or base
  • 具有命名参数的属性用法Inside attribute usages with named parameters

init 此文档中,可设置访问器的时间统称为对象的构造阶段。The times above in which the init accessors are settable are collectively referred to in this document as the construction phase of the object.

这意味着, Student 可以通过以下方式使用类:This means the Student class can be used in the following ways:

var s = new Student()
{
    FirstName = "Jared",
    LastName = "Parosns",
};
s.LastName = "Parsons"; // Error: LastName is not settable

init访问器可设置的规则围绕类型层次结构扩展。The rules around when init accessors are settable extend across type hierarchies. 如果该成员是可访问的,并且已知该对象处于构造阶段,则该成员是可设置的。If the member is accessible and the object is known to be in the construction phase then the member is settable. 这就是:That specifically allows for the following:

class Base
{
    public bool Value { get; init; }
}

class Derived : Base
{
    Derived()
    {
        // Not allowed with get only properties but allowed with init
        Value = true;
    }
}

class Consumption
{
    void Example()
    {
        var d = new Derived() { Value = true; };
    }
}

init 调用访问器时,已知实例处于开放构造阶段。At the point an init accessor is invoked, the instance is known to be in the open construction phase. 因此, init 除了一般 set 访问器可以执行的操作外,还允许访问器执行以下操作:Hence an init accessor is allowed to take the following actions in addition to what a normal set accessor can do:

  1. 调用 init 通过或提供的其他访问器 this``baseCall other init accessors available through this or base
  2. readonly通过指定在同一类型中声明的字段thisAssign readonly fields declared on the same type through this
class Complex
{
    readonly int Field1;
    int Field2;
    int Prop1 { get; init ; }
    int Prop2
    {
        get => 42;
        init
        {
            Field1 = 13; // okay
            Field2 = 13; // okay
            Prop1 = 13; // okay
        }
    }
}

readonly 字段从访问器分配的能力 init 限制为与访问器在同一类型上声明的那些字段。The ability to assign readonly fields from an init accessor is limited to those fields declared on the same type as the accessor. 它不能用于分配 readonly 基类型中的字段。It cannot be used to assign readonly fields in a base type. 此规则可确保类型作者保持对其类型的可变性行为的控制。This rule ensures that type authors remain in control over the mutability behavior of their type. 不希望利用的开发人员 init 不会受到其他类型的影响,因为选择这样做:Developers who do not wish to utilize init cannot be impacted from other types choosing to do so:

class Base
{
    internal readonly int Field;
    internal int Property
    {
        get => Field;
        init => Field = value; // Okay
    }

    internal int OtherProperty { get; init; }
}

class Derived : Base
{
    internal readonly int DerivedField;
    internal int DerivedProperty
    {
        get => DerivedField;
        init
        {
            DerivedField = 42;  // Okay
            Property = 0;       // Okay
            Field = 13;         // Error Field is readonly
        }
    }

    public Derived()
    {
        Property = 42;  // Okay 
        Field = 13;     // Error Field is readonly
    }
}

init 在虚拟属性中使用时,所有重写也必须标记为 initWhen init is used in a virtual property then all the overrides must also be marked as init. 同样,不能使用重写的简单 set initLikewise it is not possible to override a simple set with init.

class Base
{
    public virtual int Property { get; init; }
}

class C1 : Base
{
    public override int Property { get; init; }
}

class C2 : Base
{
    // Error: Property must have init to override Base.Property
    public override int Property { get; set; }
}

interface声明还可以 init 通过以下模式参与样式初始化:An interface declaration can also participate in init style initialization via the following pattern:

interface IPerson
{
    string Name { get; init; }
}

class Init
{
    void M<T>() where T : IPerson, new()
    {
        var local = new T()
        {
            Name = "Jared"
        };
        local.Name = "Jraed"; // Error
    }
}

此功能的限制:Restrictions of this feature:

  • init访问器只能用于实例属性The init accessor can only be used on instance properties
  • 属性不能同时包含 initset 访问器A property cannot contain both an init and set accessor
  • 如果基具有,则属性的所有重写都必须具有 init initAll overrides of a property must have init if the base had init. 此规则也适用于接口实现。This rule also applies to interface implementation.

只读结构Readonly structs

init 访问器 (自动实现的访问器和手动实现的访问器) 在的属性以及属性上都是允许的 readonly struct readonlyinit accessors (both auto-implemented accessors and manually-implemented accessors) are permitted on properties of readonly structs, as well as readonly properties. init 不允许 readonlyreadonly 和非类型中将访问器标记为自身 readonly structinit accessors are not permitted to be marked readonly themselves, in both readonly and non-readonly struct types.

readonly struct ReadonlyStruct1
{
    public int Prop1 { get; init; } // Allowed
}

struct ReadonlyStruct2
{
    public readonly int Prop2 { get; init; } // Allowed

    public int Prop3 { get; readonly init; } // Error
}

元数据编码Metadata encoding

属性 init 访问器将作为标准 set 访问器发出,并使用标记有 modreq 的返回类型 IsExternalInitProperty init accessors will be emitted as a standard set accessor with the return type marked with a modreq of IsExternalInit. 这是一种新类型,它具有以下定义:This is a new type which will have the following definition:

namespace System.Runtime.CompilerServices
{
    public sealed class IsExternalInit
    {
    }
}

编译器将按全名匹配类型。The compiler will match the type by full name. 它不会在核心库中显示。There is no requirement that it appear in the core library. 如果有多个使用此名称的类型,则编译器将按以下顺序关联中断:If there are multiple types by this name then the compiler will tie break in the following order:

  1. 正在编译的项目中定义的项The one defined in the project being compiled
  2. System.private.corelib 中定义的The one defined in corelib

如果这些都不存在,则将发出类型多义性错误。If neither of these exist then a type ambiguity error will be issued.

IsExternalInit此问题中进一步介绍了的设计The design for IsExternalInit is futher covered in this issue

问题Questions

中断性变更Breaking changes

编码此功能的方式的主要透视点之一将会导致以下问题:One of the main pivot points in how this feature is encoded will come down to the following question:

它是否是要替换的二进制重大 init 更改 setIs it a binary breaking change to replace init with set?

将替换 initset ,并使属性完全可写,而不会对非虚拟属性进行源重大更改。Replacing init with set and thus making a property fully writable is never a source breaking change on a non-virtual property. 它只是扩展可写入属性的方案集。It simply expands the set of scenarios where the property can be written. 相关的唯一行为是,这是否仍是二进制的重大更改。The only behavior in question is whether or not this remains a binary breaking change.

如果我们想要将更改 initset 源和二进制兼容的更改,则会强制我们在下面的 modreq 与属性决策上做出选择,因为它会将 modreqs 排除为解决方案。If we want to make the change of init to set a source and binary compatible change then it will force our hand on the modreq vs. attributes decision below because it will rule out modreqs as a solution. 另一方面,这会被视为一种不感兴趣的做法,这将使 modreq 与属性决策更少有影响力。If on the other hand this is seen as a non-interesting then this will make the modreq vs. attribute decision less impactful.

解决方法 LDM 不会出现这种情况。Resolution This scenario is not seen as compelling by LDM.

Modreqs 与属性Modreqs vs. attributes

init 元数据过程中发出时,属性访问器的发出策略必须在使用属性或 modreqs 之间进行选择。The emit strategy for init property accessors must choose between using attributes or modreqs when emitting during metadata. 它们具有不同的权衡要求。These have different trade offs that need to be considered.

使用 modreq 声明为属性集访问器批注意味着 CLI 兼容编译器将忽略访问器,除非它了解 modreq。Annotating a property set accessor with a modreq declaration means CLI compliant compilers will ignore the accessor unless it understands the modreq. 这意味着仅识别的编译器 init 将读取成员。That means only compilers aware of init will read the member. 不识别的编译器 init 会忽略 set 访问器,因此不会意外地将属性视为读/写。Compilers unaware of init will ignore the set accessor and hence will not accidentally treat the property as read / write.

Modreq 的缺点 init 成为访问器的二进制签名的一部分 setThe downside of modreq is init becomes a part of the binary signature of the set accessor. 添加或删除 init 会破坏应用程序的二进制 compatbility。Adding or removing init will break binary compatbility of the application.

使用特性来批注 set 访问器意味着只有了解该属性的编译器知道它会限制对它的访问。Using attributes to annotate the set accessor means that only compilers which understand the attribute will know to limit access to it. 不知道的编译器 init 会将其视为简单的读/写属性,并允许访问。A compiler unaware of init will see it as a simple read / write property and allow access.

这似乎表明,这一决定是在额外安全方面做出的选择,但代价是在二进制兼容性的代价。This would seemingly mean this decision is a choice between extra safety at the expense of binary compatibility. 深入,额外的安全性并不是它的真正含义。Digging in a bit the extra safety is not exactly what it seems. 它不会在以下情况下受到保护:It will not protect against the following circumstances:

  1. 成员反射 publicReflection over public members
  2. 使用 dynamicThe use of dynamic
  3. 不识别 modreqs 的编译器Compilers that don't recognize modreqs

还应考虑到,当我们完成 .NET 5 的 IL 验证规则时,它 init 将是这些规则之一。It should also be considered that, when we complete the IL verification rules for .NET 5, init will be one of those rules. 这意味着只需验证发出可验证 IL 的编译器即可获得额外的强制。That means extra enforcement will be gained from simply verifying compilers emitting verifiable IL.

.NET (c #、F # 和 VB) 的主要语言将进行更新以识别这些 init 访问器。The primary languages for .NET (C#, F# and VB) will all be updated to recognize these init accessors. 因此,这种情况下唯一现实的方案是: c # 9 编译器发出 init 属性,这些属性由较旧的工具集(如 c # 8、VB 15 等)发现。C # 8。Hence the only realistic scenario here is when a C# 9 compiler emits init properties and they are seen by an older toolset such as C# 8, VB 15, etc ... C# 8. 这就是要考虑和权衡二进制兼容性的权衡。That is the trade off to consider and weigh against binary compatibility.

注意 本讨论主要适用于成员,不适用于字段。Note This discussion primarily applies to members only, not to fields. 尽管 init 字段被 LDM 拒绝,但对于 modreq 与属性讨论仍有兴趣。While init fields were rejected by LDM they are still interesting to consider for the modreq vs. attribute discussion. init字段的功能是现有限制的 relaxation readonlyThe init feature for fields is a relaxation of the existing restriction of readonly. 这意味着,如果将这些字段作为 readonly + a 属性发出,则较早的编译器不会出现错误,因为它们已被识别 readonlyThat means if we emit the fields as readonly + an attribute there is no risk of older compilers mis-using the field because they would already recognize readonly. 因此,在这里使用 modreq 不会添加任何额外的保护。Hence using a modreq here doesn't add any extra protection.

解决方法 此功能将使用 modreq 对属性 setter 进行编码 initResolution The feature will use a modreq to encode the property init setter. 有说服力的因素是 (的,无特定顺序) :The compelling factors were (in no particular order):

  • 希望防止旧的编译器违反 init 语义Desire to discourage older compilers from violating init semantics
  • 希望 init 在声明中添加或删除 virtual ,或者同时执行 interface 源和二进制的重大更改。Desire to make adding or removing init in a virtual declaration or interface both a source and binary breaking change.

如果对删除是二进制兼容更改,也不会有重要支持, init 则可以选择直接使用 modreq。Given there was also no significant support for removing init to be a binary compatible change it made the choice of using modreq straight forward.

init 与 initonlyinit vs. initonly

有三种语法形式,可在我们的 LDM 会议中获得重要的注意事项:There were three syntax forms which got significant consideration during our LDM meeting:

// 1. Use init 
int Option1 { get; init; }
// 2. Use init set
int Option2 { get; init set; }
// 3. Use initonly
int Option3 { get; initonly; }

解决方法 LDM 中没有充分的语法。Resolution There was no syntax which was overwhelmingly favored in LDM.

需要注意的一点是,对语法的选择如何影响我们 init 在将来作为一般功能执行成员的能力。One point which got significant attention was how the choice of syntax would impact our ability to do init members as a general feature in the future. 如果选择选项1,则意味着在将来定义具有样式方法的属性是很困难的 init getChoosing option 1 would mean that it would be difficult to define a property which had an init style get method in the future. 最终,如果我们决定以后使用常规成员继续操作,则 init 可以 init 在属性访问器列表中提供修饰符,并为提供一个简短的 init setEventually it was decided that if we decided to go forward with general init members in future, we could allow init to be a modifier in the property accessor list as well as a short hand for init set. 实质上,下面两个声明是相同的。Essentially the following two declarations would be identical.

int Property1 { get; init; }
int Property1 { get; init set; }

init 属性访问器列表中,决定以独立访问器的形式继续。The decision was made to move forward with init as a standalone accessor in the property accessor list.

初始化失败时发出警告Warn on failed init

请考虑以下场景。Consider the following scenario. 类型声明的 init 唯一成员未在构造函数中设置。A type declares an init only member which is not set in the constructor. 如果构造对象的代码未能初始化值,是否应收到警告?Should the code which constructs the object get a warning if they failed to initialize the value?

此时,该字段将永远不会被设置,因此,可能会有许多相似之处和有关无法初始化数据的警告 privateAt that point it is clear the field will never be set and hence has a lot of similarities with the warning around failing to initialize private data. 那么,在这种情况下面会出现一个警告,Hence a warning would seemingly have some value here?

不过,此警告有重大缺点:There are significant downsides to this warning though:

  1. 它会使更改到的兼容性故事复杂化 readonly initIt complicates the compatibility story of changing readonly to init.
  2. 它需要额外的元数据来表示需要由调用方初始化的成员。It requires carrying additional metadata around to denote the members which are required to be initialized by the caller.

此外,如果我们认为在这种情况下,我们认为在这种情况下,强制对象创建者收到警告/错误应对特定字段存在价值,这可能是一项常见功能。Further if we believe there is value here in the overall scenario of forcing object creators to be warned / error'd about specific fields then this likely makes sense as a general feature. 没有理由只应限制为 init 成员。There is no reason it should be limited to just init members.

解决方法 字段和属性的使用不会出现警告 initResolution There will be no warning on consumption of init fields and properties.

LDM 希望更广泛地讨论必填字段和属性的概念。LDM wants to have a broader discussion on the idea of required fields and properties. 这可能会导致我们返回并重新考虑我们的 init 成员和验证的位置。That may cause us to come back and reconsider our position on init members and validation.

允许 init 作为字段修饰符Allow init as a field modifier

同样, init 也可以作为属性访问器,它也可以作为字段的指定,将其作为 init 属性提供。In the same way init can serve as a property accessor it could also serve as a designation on fields to give them similar behaviors as init properties. 这将允许在类型、派生类型或对象初始值设定项完成构造之前,对字段进行赋值。That would allow for the field to be assigned before construction was complete by the type, derived types, or object initializers.

class Student
{
    public init string FirstName;
    public init string LastName;
}

var s = new Student()
{
    FirstName = "Jarde",
    LastName = "Parsons",
}

s.FirstName = "Jared"; // Error FirstName is readonly

在元数据中,这些字段将以与字段相同的方式进行标记, readonly 但使用附加的属性或 modreq 指示它们是 init 样式字段。In metadata these fields would be marked in the same way as readonly fields but with an additional attribute or modreq to indicate they are init style fields.

解决方法 LDM 同意此建议是听起来的,但是总体情况下,这种情况并不完全相同。Resolution LDM agrees this proposal is sound but overall the scenario felt disjoint from properties. 这里的决策是只继续进行 init 属性。The decision was to proceed only with init properties for now. 由于 init 属性可以 readonly 在属性的声明类型上改变字段,因此它具有适当的灵活性级别。This has a suitable level of flexibility as an init property can mutate a readonly field on the declaring type of the property. 如果有大量客户反馈来表明方案,则会很少见。This will be reconsidered if there is significant customer feedback that justifies the scenario.

允许 init 作为类型修饰符Allow init as a type modifier

在中 readonly ,可以将修饰符应用于 struct 来自动将所有字段声明为 readonlyinit 也可以在上声明唯一修饰符,并将 struct class 所有字段自动标记为 initIn the same way the readonly modifier can be applied to a struct to automatically declare all fields as readonly, the init only modifier can be declared on a struct or class to automatically mark all fields as init. 这意味着以下两个类型声明是等效的:This means the following two type declarations are equivalent:

struct Point
{
    public init int X;
    public init int Y;
}

// vs. 

init struct Point
{
    public int X;
    public int Y;
}

解决方法 此功能在此处过于 刻意 ,并与 readonly struct 它所基于的功能冲突。Resolution This feature is too cute here and conflicts with the readonly struct feature on which it is based. readonly struct 功能很简单,因为它适用于 readonly 所有成员:字段、方法等。此 init struct 功能仅适用于属性。The readonly struct feature is simple in that it applies readonly to all members: fields, methods, etc ... The init struct feature would only apply to properties. 这实际上会使用户感到困惑。This actually ends up making it confusing for users.

假设 init 只对某个类型的某些方面有效,我们拒绝将其作为类型修饰符。Given that init is only valid on certain aspects of a type, we rejected the idea of having it as a type modifier.

注意事项Considerations

兼容性Compatibility

init 功能旨在与现有的 get 属性兼容。The init feature is designed to be compatible with existing get only properties. 具体而言,它只是一个属性的完全累加性更改,该属性 get 仅在今天,但需要更多的 flexbile 对象创建语义。Specifically it is meant to be a completely additive change for a property which is get only today but desires more flexbile object creation semantics.

例如,考虑以下类型:For example consider the following type:

class Name
{
    public string First { get; }
    public string Last { get; }

    public Name(string first, string last)
    {
        First = first;
        Last = last;
    }
}

添加 init 到这些属性中是非重大更改:Adding init to these properties is a non-breaking change:

class Name
{
    public string First { get; init; }
    public string Last { get; init; }

    public Name(string first, string last)
    {
        First = first;
        Last = last;
    }
}

IL 验证IL verification

当 .NET Core 决定重新实现 IL 验证时,需要对规则进行调整,以便为成员提供帐户 initWhen .NET Core decides to re-implement IL verification, the rules will need to be adjusted to account for init members. 这将需要包含在规则更改中,以便对数据进行非转变性的更改 readonlyThis will need to be included in the rule changes for non-mutating acess to readonly data.

IL 验证规则需要分为两部分:The IL verification rules will need to be broken into two parts:

  1. 允许 init 成员设置 readonly 字段。Allowing init members to set a readonly field.
  2. 确定何时 init 可以合法地调用成员。Determining when an init member can be legally called.

第一种是对现有规则的简单调整。The first is a simple adjustment to the existing rules. 可以学会使用 IL 验证程序来识别 init 成员,而只需考虑在 readonly 此类成员上可设置的字段 thisThe IL verifier can be taught to recognize init members and from there it just needs to consider a readonly field to be settable on this in such a member.

第二个规则更复杂。The second rule is more complicated. 在对象初始值设定项的简单情况下,该规则是直接的。In the simple case of object initializers the rule is straight forward. init当表达式的结果 new 仍在堆栈上时,调用成员应合法。It should be legal to call init members when the result of a new expression is still on the stack. 也就是说,在将值存储在本地数组元素或字段中,或将其作为参数传递给另一种方法时,它仍合法调用 init 成员。That is until the value has been stored in a local, array element or field or passed as an argument to another method it will still be legal to call init members. 这可确保一旦表达式的结果 new 被发布到命名标识符 (而不是) ,就 this 不会再合法调用 init 成员。This ensures that once the result of the new expression is published to a named identifier (other than this) then it will no longer be legal to call init members.

尽管我们混合了 init 成员、对象初始值设定项和,但这种情况更复杂 awaitThe more complicated case though is when we mix init members, object initializers and await. 这可能会导致新创建的对象暂时提升到状态机中,因而放入字段中。That can cause the newly created object to be temporarily hoisted into a state machine and hence put into a field.

var student = new Student() 
{
    Name = await SomeMethod()
};

在此之前,的结果 new Student() 将作为字段 hoised 到状态机中 NameHere the result of new Student() will be hoised into a state machine as a field before the set of Name occurs. 编译器需要将此类已提升的字段标记为 IL 验证程序理解它们不是用户可访问的方式,因此不违反的预期语义 initThe compiler will need to mark such hoisted fields in a way that the IL verifier understands they're not user accessible and hence doesn't violate the intended semantics of init.

init 成员init members

init修饰符可以扩展以应用于所有实例成员。The init modifier could be extended to apply to all instance members. 这会在 init 对象构造过程中通用化概念,并允许类型声明可在构造过程中 partipate 以初始化字段和属性的帮助器方法 initThis would generalize the concept of init during object construction and allow types to declare helper methods that could partipate in the construction process to initialize init fields and properties.

此类成员将具有 init 访问器在此设计中执行的所有 restricions。Such members would have all the restricions that an init accessor does in this design. 不过,这种需要是值得怀疑的,而这可以通过一种兼容的方式安全地添加在语言的未来版本中。The need is questionable though and this can be safely added in a future version of the language in a compatible manner.

生成三个访问器Generate three accessors

属性的一个可能实现 initinit 完全独立于 setOne potential implementation of init properties is to make init completely separate from set. 这意味着,一个属性可能具有三个不同的访问器: getsetinitThat means that a property can potentially have three different accessors: get, set, and init.

这可能会带来的优势是,允许使用 modreq 来强制实现正确性,同时保持二进制兼容性。This has the potential advantage of allowing the use of modreq to enforce correctness while maintaining binary compatibility. 实现大致如下所示:The implementation would roughly be the following:

  1. init如果有,则始终发出访问器 setAn init accessor is always emitted if there is a set. 当开发人员未定义时,它只是对的引用 setWhen not defined by the developer it is simply a reference to set.
  2. 对象初始值设定项中的属性集将始终使用( init 如果存在),但如果缺少,则返回 setThe set of a property in an object initializer will always use init if present but fall back to set if it's missing.

这意味着开发人员始终可以 init 从属性中安全地删除。This means that a developer can always safely delete init from a property.

这种设计的缺点是,仅 init 当在出现时 始终 发出时才有用 setThe downside of this design is that is only useful if init is always emitted when there is a set. 如果以前删除了该语言 init ,则它必须假定它是,因此 init 必须始终发出。The language can't know if init was deleted in the past, it has to assume it was and hence the init must always be emitted. 这会导致一种重要的元数据扩展,而不是在此处提供兼容性的代价。That would cause a significant metadata expansion and is simply not worth the cost of the compatibility here.