C#9.0:Init相關(guān)總結(jié)
背景
在以前的C#版本里面,如果需要定義一個(gè)不可修改的的類型的做法一般是:聲明為readonly,并設(shè)置為只包含get訪問(wèn)器,不包含set訪問(wèn)器。如下:
public class PersonInfo { /// <summary> /// 身份編號(hào) /// </summary> public string UserCode { get; } /// <summary> /// 姓名 /// </summary> public string UserName { get; } /// <summary> /// 初始化賦值 /// </summary> /// <param name="_userCode"></param> /// <param name="_userName"></param> public PersonInfo(string _userCode,string _userName) { UserCode = _userCode; UserName = _userName; } }
這種方式是可行的,也達(dá)到我們的目的,但是代碼量多,需要增加額外的構(gòu)造方法來(lái)實(shí)現(xiàn)初始化賦值,并且如果字段越多,帶參構(gòu)造函數(shù)也會(huì)越大,開發(fā)工作量也越大,更不好維護(hù)。
為了改變這種狀態(tài),C#9.0提供了一種解決方案:在對(duì)象初始換的時(shí)候就配置為只讀的方式。
特別對(duì)一口氣創(chuàng)建含有嵌套結(jié)構(gòu)的樹狀對(duì)象來(lái)說(shuō)更有用。下面是一個(gè)用戶信息初始化的案例:
PersonInfo pi = new PersonInfo() { UserCode="1234567890", UserName="Brand" };
從這個(gè)例子說(shuō)明了,要進(jìn)行對(duì)象初始化,我們必須先要在需要初始化的屬性中添加set訪問(wèn)器,然后才能在對(duì)象初始化器中通過(guò)給屬性或者索引器賦值來(lái)實(shí)現(xiàn)。如下:
public class PersonInfo { /// <summary> /// 身份編號(hào) /// </summary> public string UserCode { get; set; } /// <summary> /// 姓名 /// </summary> public string UserName { get; set; } }
所以對(duì)于初始化來(lái)說(shuō),屬性必須是可變的,set訪問(wèn)器就必須存在。這就是問(wèn)題所在,很多情況下為了避免屬性初始化之后再被改變,就需要不可變對(duì)象類型,因此setter訪問(wèn)器在這里明顯不適用。
基于這種有這種常見的需要和局限性,C#9.0引入了只用來(lái)初始化的init設(shè)置訪問(wèn)器。這時(shí),上面的PersonInfo類就可以定義成下面的樣子:
public class PersonInfo { /// <summary> /// 身份編號(hào) /// </summary> public string? UserCode { get; init; } /// <summary> /// 姓名 /// </summary> public string? UserName { get; init; } }
這邊通過(guò)采用init訪問(wèn)器,代碼變得簡(jiǎn)潔易懂了,滿足了上面的只讀需求,而且更易編碼和維護(hù)。
定義和使用
init(只初始化屬性或索引器訪問(wèn)器):只在對(duì)象構(gòu)造階段進(jìn)行初始化時(shí)可以用來(lái)賦值,算是set訪問(wèn)器的變體,set訪問(wèn)器的位置使用init來(lái)替換。init有著如下限制:
1、init訪問(wèn)器只能用在實(shí)例屬性或索引器中,靜態(tài)屬性或索引器中不可用。
2、屬性或索引器不能同時(shí)包含init和set兩個(gè)訪問(wèn)器
3、如果基類的屬性有init,那么屬性或索引器的所有相關(guān)重寫,都必須有init。接口也一樣。
什么時(shí)候設(shè)置init訪問(wèn)器
除過(guò)在局部方法和lambda表達(dá)式中,帶有init訪問(wèn)器的屬性和索引器可以在下面幾種情況中可設(shè)置的。這幾個(gè)設(shè)置的時(shí)機(jī)都是在對(duì)象的構(gòu)造階段。過(guò)了構(gòu)造階段,后續(xù)賦值操作就不允許了。
1、在對(duì)象初始化器工作期間
2、在with表達(dá)式初始化器工作期間
3、在所處或者派生的類型的實(shí)例構(gòu)造函數(shù)中,在this或者base使用上
4、在任意屬性init訪問(wèn)器里面,在this或者base使用上
5、在帶有命名參數(shù)的attribute使用中
在這些限制條件下,意味著我們上面定義的PersonInfo只能在對(duì)象初始化的時(shí)候使用,第二次賦值就不被允許了。
即:一旦初始化完成之后,只初始化屬性或索引就保護(hù)著對(duì)象的狀態(tài)免于改變。
var person = new PersonInfo() { UserCode="12345678", UserName="Brand" }; //提示錯(cuò)誤:只能在對(duì)象初始器或?qū)嵗龢?gòu)造函數(shù)中分配 init-only person.UserName = "Brand1";
init屬性訪問(wèn)器和只讀字段
因?yàn)閕nit訪問(wèn)器只能在初始化時(shí)被調(diào)用,所以在init屬性訪問(wèn)器中可以改變封閉類的只讀字段。
需要注意的是,從init訪問(wèn)器中來(lái)給readonly字段賦值僅限于跟init訪問(wèn)器處于同一類型中定義的字段,通過(guò)它是不能給父類中定義的readonly字段賦值的,關(guān)于這繼承有關(guān)的示例,我們會(huì)在2.4類型間的層級(jí)傳遞中看到。
public class PersonInfo { private readonly string userCode = "<unknown>"; private readonly string userName = "<unknown>"; public string UserCode { get => userCode; init => userCode = (value ?? throw new ArgumentNullException(nameof(UserCode))); } public string UserName { get => userName; init => userName = (value ?? throw new ArgumentNullException(nameof(UserName))); } }
類型層級(jí)間的傳遞
我們知道只包含get訪問(wèn)器的屬性或索引器只能在所處類的自身構(gòu)造函數(shù)中被初始化,但init訪問(wèn)器可以進(jìn)行設(shè)置的規(guī)則是可以跨類型層級(jí)傳遞的。
帶有init訪問(wèn)器的成員只要是可訪問(wèn)的,對(duì)象實(shí)例并能在構(gòu)造階段被知曉,那這個(gè)成員就是可設(shè)置的。
1、在對(duì)象初始化中使用,是允許的
public class PersonInfo { /// <summary> /// 身份編號(hào) /// </summary> public string UserCode { get; init; } /// <summary> /// 姓名 /// </summary> public string UserName { get; init; } public PersonInfo() { UserCode = "1234567890"; UserName = "Brand"; } }
2、在派生類的實(shí)例構(gòu)造函數(shù)中,也是允許的,如下面這兩個(gè)例子:
public class PersonInfoExt : PersonInfo { public PersonInfoExt() { UserCode = "1234567890_0"; UserName = "Brand1"; } }
var personext = new PersonInfoExt() { UserCode="1234567890_2", UserName="Brand2" };
從init訪問(wèn)器能被調(diào)用這一方面來(lái)看,對(duì)象實(shí)例在開放的構(gòu)造階段就可以被知曉。因此除過(guò)正常set可以做之外,init訪問(wèn)器的下列行為也是被允許的。
1、通過(guò)this或者base調(diào)用其他可用的init訪問(wèn)器
2、在同一類型中定義的readonly字段,是可以通過(guò)this給賦值的
init中是不能更改父類中的readonly字段的,只能更改本類中readonly字段。示例代碼如下:
class PersonInfo1 { protected readonly string UserCode_R; public String UserCode { get => UserCode_R; init => UserCode_R = value; // 正確:在同一類中定義的readonly屬性,可以直接通過(guò)this給賦值的 } internal String UserName { get; init; } } class PersonInfo1Ext : PersonInfo1 { protected readonly int NewField; internal int NewProp { get => NewField; init { NewField = 100; // 正確 UserCode = "123456"; // 正確 UserCode_R = "1234567"; // 出錯(cuò),試圖修改基類中的readonly字段UserCode_R } } public PersonInfo1Ext() { UserCode = "123456"; // 正確 UserCode_R = "1234567"; // 出錯(cuò),試圖修改基類中的readonly字段UserCode_R } }
如果init被用于virtual修飾的屬性或者索引器,那么所有的覆蓋重寫都必須被標(biāo)記為init,是不能用set的。同樣地,我們不可能用init來(lái)覆蓋重寫一個(gè)set的。
public class PersonInfo { /// <summary> /// 身份編號(hào) /// </summary> public virtual string UserCode { get; init; } /// <summary> /// 姓名 /// </summary> public virtual string UserName { get; set; } } public class PersonInfoExt1 : PersonInfo { public override string UserCode { get; init; } public override string UserName { get; set; } } public class PersonInfoExt2 : PersonInfo { // 錯(cuò)誤: 基類的init屬性必須由init來(lái)重寫PersonInfo.UserCode public override int UserCode { get; set; } // 錯(cuò)誤: 基類的init屬性必須由set來(lái)重寫PersonInfo.UserName public override string UserName { get; init; } }
init在接口接口中應(yīng)用
一個(gè)接口中的默認(rèn)實(shí)現(xiàn),也是可以采用init進(jìn)行初始化,下面就是一個(gè)應(yīng)用模式示例。
interface IPersonInfo { string Usercode { get; init; } string UserName { get; init; } } class PersonInfo { void NewPersonInfo<T>() where T : IPersonInfo, new() { var person = new T() { Usercode = "1234567890", UserName = "Jerry" }; person.Usercode = "111"; // 錯(cuò)誤 } }
init訪問(wèn)器是允許在readonly struct中的屬性中使用的,init和readonly的目標(biāo)都是一致的,就是只讀。示例代碼如下:
readonly struct PersonInfo { /// <summary> /// 身份編號(hào) /// </summary> public string UserCode { get; init; } /// <summary> /// 姓名 /// </summary> public string UserName { get; set; } }
但是要注意的是:
1、不管是readonly結(jié)構(gòu)還是非readonly結(jié)構(gòu),不管是手工定義屬性還是自動(dòng)生成屬性,init都是可以使用的。
2、init訪問(wèn)器本身是不能標(biāo)記為readonly的。但是所在屬性或索引器可以被標(biāo)記為readonly
struct PersonInfo { /// <summary> /// 身份編號(hào) /// </summary> public readonly string UserCode { get; init; } /// <summary> /// 姓名 /// </summary> public string UserName { get; readonly init; } }
以上就是C#9.0:Init相關(guān)總結(jié)的詳細(xì)內(nèi)容,更多關(guān)于C#9.0:Init的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
mvc C# JavaScript LigerUI oracle實(shí)現(xiàn)用戶的注冊(cè)、登陸驗(yàn)證、登陸
這篇文章主要介紹了mvc C# JavaScript LigerUI oracle實(shí)現(xiàn)用戶的注冊(cè)、登陸驗(yàn)證、登陸的相關(guān)資料,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-04-04C#中使用NLog庫(kù)進(jìn)行日志記錄的流程詳解
NLog 是 .NET 的日志記錄框架,具有豐富的日志路由和管理能力,極大地幫助您生成和管理日志,NLog 是一個(gè)庫(kù),可以輕松地同時(shí)記錄和管理多個(gè)不同區(qū)域中的數(shù)據(jù),本文將給大家介紹在C#中使用 NLog 庫(kù)進(jìn)行日志記錄的教程,需要的朋友可以參考下2024-06-06C#實(shí)現(xiàn)運(yùn)行狀態(tài)堆疊柱狀圖
這篇文章主要為大家詳細(xì)介紹了C#實(shí)現(xiàn)運(yùn)行狀態(tài)堆疊柱狀圖,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02C#以流方式讀socket超時(shí)設(shè)置的實(shí)例
這篇文章主要為大家詳細(xì)介紹了C#以流方式讀socket超時(shí)設(shè)置的實(shí)例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-03-03C#實(shí)現(xiàn)自定義光標(biāo)并動(dòng)態(tài)切換
這篇文章主要為大家詳細(xì)介紹了如何利用C#語(yǔ)言實(shí)現(xiàn)自定義光標(biāo)、并動(dòng)態(tài)切換光標(biāo)類型,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2022-07-07C#實(shí)現(xiàn)將文件轉(zhuǎn)換為XML的方法
這篇文章主要介紹了C#實(shí)現(xiàn)將文件轉(zhuǎn)換為XML的方法,實(shí)例分析了office文件與xml的相互轉(zhuǎn)換技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-12-12