僅初始化 SetterInit Only Setters

總結Summary

本提案會將 init only 屬性和索引子的概念新增至 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

在 c # 中建立不可變數據的基礎機制尚未在1.0 之後變更。The underlying mechanisms for building immutable data in C# haven't changed since 1.0. 它們仍然存在:They remain:

  1. 將欄位宣告為 readonlyDeclaring fields as readonly.
  2. 宣告只包含存取子的屬性 getDeclaring 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存取子可讓呼叫者在結構的 act 期間改變成員,讓變得更有彈性的物件。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 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
  • 在包含或衍生類型的實例函式內,位於 thisbaseInside an instance constructor of the containing or derived type, on this or base
  • init在或上任何屬性的存取子 thisbaseInside 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 除了一般存取子可以執行的動作之外,還允許存取子採取下列動作 setHence 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 結構Readonly structs

init 存取子 (自動實作為存取子以及手動實作為存取子的) 在的屬性上允許 readonly struct ,以及 readonly 屬性。init 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. 在 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 set 嗎?Is 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.

如果我們想要變更 init set 來源和二進位相容的變更,則它會在 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欄位的功能是的現有限制放寬 readonlyThe init feature for fields is a relaxation of the existing restriction of readonly. 這表示,如果我們以 + 屬性的形式發出欄位, readonly 舊版編譯器就不會有任何風險,因為它們已經辨識過了 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 來編碼屬性 init setter。Resolution 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 與 initonly 的比較init 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. 此決定是要立即使用屬性來進行 initThe 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. 如果有大量的客戶意見反應可證明案例,這將會 reconsidered。This will be reconsidered if there is significant customer feedback that justifies the scenario.

允許 init 作為類型修飾詞Allow init as a type modifier

readonly如同可以將修飾詞套用至 struct 來自動將所有欄位宣告為 readonly ,唯一的修飾詞 init 可以在上宣告,也可以 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 驗證時,將需要調整規則以考慮 init 成員。When .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 this 這類成員中可設定的欄位。The 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. 也就是說,在將值儲存在本機、陣列元素或欄位中,或將它當作引數傳遞至另一個方法時,呼叫成員仍然是合法的 initThat 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 則呼叫成員將不會再合法 initThis 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 的 helper 方法,以初始化 init 欄位和屬性。This 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

其中一個可能的 init 屬性實作為 init 完全不同 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.