前言
心血來潮,這篇講點基礎的東西。
Field
比起 Property,Field 很不起眼,你若問 JavaScript,它甚至都沒有 Field。
但在 C#,class 裏頭真正裝 value 的其實是 Field,Property 只是 Filed 的讀寫器而已。
Field 長這樣
public class Person { public int Age; }
使用
var person = new Person(); Console.WriteLine(person.Age); // int default is 0 person.Age = 10; // set value Console.WriteLine(person.Age); // get value: 10
readonly and init value
如果一個 Field 只能讀,不能寫,可以使用 readonly keyword 去聲明它。
public class Person { public readonly int Age; } var person = new Person(); person.Age = 10; // Error : A readonly field cannot be assigned
我們可以用 2 種方式 set init value
public class Person { public readonly int Age = 1; public Person() { Age = 2; } }
一個是在 Field 的結尾寫等於,另一個是通過 construtor。如果兩個都 set,construtor 會 override 掉結尾等於。
注:下面這個寫法是不 ok 的,它無法突破 readonly 的限制哦。
var person = new Person { Age = 15 // Error: A readonly field cannot be assigned };
Field 的日常(Dependancy Injection)
平時很少會使用 Field 的,絕大部分情況我們用 Property,除了 Dependancy Injection。
public class HomeModel : PageModel { private readonly ILogger _logger; public HomeModel(ILogger<HomeModel> logger) { _logger = logger; } public void OnGet() { _logger.LogInformation("Hello World"); } }
一個 readonly Field,通過 construtor set init value,然後使用。
C# 12.0 primary construtor 的寫法
public class HomeModel(ILogger<HomeModel> logger) : PageModel { public void OnGet() { logger.LogInformation("Hello World"); } }
primary construtor 的 parameter 會被 compile 成 private Field,所以上面的代碼和上一 part 的代碼基本上是一樣的。
唯一的不同是 primary construtor 目前無法聲明 readonly Field。所以如果我們要 readonly 那就得多加一行。
public class HomeModel(ILogger<HomeModel> logger) : PageModel { private readonly ILogger _logger = logger; public void OnGet() { _logger.LogInformation("Hello World"); logger.LogInformation("Hello World"); // 注意:logger 依然是可用的,因爲它就是一個 Field 啊 } }
Property
Property 是 Filed 的讀寫器,它長這樣。
public class Person() { private int _age; public int Age { get { return _age; } set { _age = value; } } }
Filed 負責保存 value,Property 負責攔截讀寫過程。
這個是完整的寫法,但日常生活中,大部分情況我們會用語法糖。
Auto Property
public class Person() { public int Age { get; set; } }
Auto Property 是一種語法糖寫法,前面是 Field,後面配上一個 { get; set; } 表達。它 compile 後長這樣。
compiler 會替我們拆開它們。最終任然是一個 Field、一個 get 方法、一個 set 方法。
readonly = no set
public class Person() { public int Age { get; } }
把 set 去掉就相等於 readonly Filed。注:沒有 readonly Property 的,只有 no set。
set init value 的規則和 Field 一樣。
public class Person { public int Age { get; } = 1; public Person() { Age = 2; } } var person = new Person { Age = 3 // Error: Property or indexer 'Person.Age' cannot be assigned to -- it is read only }
init keyword
上面例子中,實例化 Person 時,賦值是會報錯的。這個是 Field 的規則,我們只可以通過 construtor parameter 去實現 init 賦值。
但這種寫法很多餘,於是 C# 6.0 多了一個 init keyword。
public int Age { get; init; } = 1; var person = new Person { Age = 3 // no more error };
把 set 換成 init,它相等於 readonly 但是又允許實例化時賦值。
required keyword
public class Person { public string Name { get; } // Warning Non-nullable property 'Name' must contain a non-null value }
這是一個 readonly Property,它有一個 warning,因爲我沒有聲明 init value。
public class Person { public string Name { get; } = "init value"; public Person() { Name = "init value"; } }
我可以通過 2 種方式去設置 init value。這樣就不會有 warning 了。
但是...如果它時 init keyword 呢?
public class Person { public string Name { get; init; } // Warning: Non-nullable property 'Name' must contain a non-null value }
init 允許我們在實例化時纔給予 init value,也就是說 class 內是可以不需要聲明 init value 的。
但這導致它又 Warning 了。爲了解決這個問題,C# 11 推出了 required keyword。
public class Person { public required string Name { get; init; } }
聲明 required keyword 後,它就不會 Warning 了。與此同時
var person = new Person(); // Error: Required member 'Person.Name' must be set
如果在實例化時忘記給予 init value,它還會報錯提醒我們哦。
泛型限制也會報錯哦
public class PersonOptions { public required string Name { get; set; } } public class Person<T> where T : new() // 泛型限制 T 必須允許無參數實例化 { } var person = new Person<PersonOptions>(); // Error : 'PersonOptions' cannot satisfy the 'new()' constraint
總結
這篇介紹了 class, filed, property, readonly, get, set, init, required 的基本使用方式。
它之所以有點混亂,主要是因爲 C# 是經過了許多版本才一點一點逐步推出這些特性的。