1. 程式人生 > >C# 9.0 新特性預覽 - init-only 屬性

C# 9.0 新特性預覽 - init-only 屬性

# C# 9.0 新特性預覽 - init-only 屬性 ### 前言 隨著 .NET 5 釋出日期的日益臨近,其對應的 C# 新版本已確定為 C# 9.0,其中新增加的特性(或語法糖)也已基本鎖定,本系列文章將向大家展示它們。 ### 目錄 [[C# 9.0 新特性預覽 - 型別推導的 new](https://www.cnblogs.com/Rwing/p/csharp-9-0-preview-target-typed-new.html)] [[C# 9.0 新特性預覽 - 空引數校驗](https://www.cnblogs.com/Rwing/p/csharp-9-0-preview-null-arg-checking.html)] [[C# 9.0 新特性預覽 - 頂級語句](https://www.cnblogs.com/Rwing/p/csharp-9-0-preview-top-level-statements.html)] [C# 9.0 新特性預覽 - init-only 屬性] [C# 9.0 新特性預覽 - Record 型別] [C# 9.0 新特性預覽 - 模式匹配的改善] [C# 9.0 新特性預覽 - 原始碼生成器] [C# 9.0 新特性預覽 - 其他小的變化] ### 只初始化 setter (Init Only Setters) 這個特性允許建立只初始化(init only)的屬性和索引器,使得 C# 中的不可變模型更加靈活。 #### 背景 在此之前,我們建立實體類/POCO類/DTO類等等模型類的時候,都期望屬性只讀不允許從外部修改,會將屬性的 setter 設為私有或者乾脆不設定 setter,例如: ```csharp public class Person { public string Name { get; private set; } // OR //public string Name { get; } } ``` 再新增一個擁有全部屬性作為簽名的構造方法: ```csharp ... public Person(string name) { this.Name = name; } ... ``` 這樣做雖然可以達到目的,但是帶來了兩個問題 1.假如屬性增多,會帶來工作量的成倍增加,以及不易維護 2.無法使用物件初始化器(object initializers) ```csharp var person = new Person { Name = "Rwing" // Compile Error }; ``` 在這個情況下,init 關鍵字應運而生了。 #### 語法 語法很簡單,只需要將屬性中的 set 關鍵字替換為 init 即可: ```csharp public string Name { get; init; } ``` 以上程式碼會被大致翻譯為: ```csharp private readonly string _name; public string Name { get { return _name; } set { _name = value;} } ``` 可以看到,與 set 的區別是,init 會為背後的欄位新增 readonly 關鍵字。 這樣我們就可以去掉一堆屬性的構造方法轉而使用物件初始化器了,並且達到了不可變的目的。 ```csharp var person = new Person { Name = "Rwing" }; // 初始化後無法再次修改 person.Name = "Foo"; // Error: Name is not settable ``` 這一語法,有很多場景需要配合同樣在 C# 9.0 中新增的 record 型別使用。 #### 哪些情況下可以被設定 - 通過物件初始化器 - 通過 with 表示式 - 在自身或者派生類的構造方法中 - 在標記為 init 的屬性中 - 在特性(attribute)類的命名引數屬性中 以上場景不難理解,但是值得一提的是,只有 get 的屬性是不可以派生類的構造方法中賦值的,但是 init 可以: ```csharp class Base { public bool Foo { get; init; } public bool Bar { get; } } class Derived : Base { Derived() { Foo = true; Bar = true; // ERROR } } ``` 此外有一種例外, 在以上場景中的 lambda 或本地函式中,也不允許被設定,例如: 原因也很簡單,lambda 或本地函式在編譯後已經不在建構函式中了。 ```csharp public class Class { public string Property { get; init; } Class() { System.Action a = () => { Property = null; // ERROR }; local(); void local() { Property = null; // ERROR } } } ``` ### 參考 [[Proposal: Init Only Setters](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/init.md)] [[InitOnlyMemberTests.cs](https://github.com/dotnet/roslyn/blob/master/src/Compilers/CSharp/Test/Semantic/Semantics/InitOnlyMemberTes