前言
C# 提供的 init
關鍵字用于在屬性中定義訪問器方法,可以讓屬性僅能在對象初始化的時候被賦值,其他時候只能為只讀屬性的形式。
例如下面代碼可以正常執行:
public?class?Demo
{public?string?Name?{?get;?init;?}
}var?demo?=?new?Demo?{?Name?=?"Demo"?};
但是當我們想修改屬性值時,就會報錯:
demo.Name?=?"My?IO";
看來只能初始化時賦值了,真的是這樣嗎?
原理
首先,我們來看看 init
最后會編譯成什么。
反編譯代碼,發現生成的代碼和普通屬性完全一致:
public?class?Demo
{//?Token:?0x17000001?RID:?1//?(get)?Token:?0x06000003?RID:?3?RVA:?0x00002085?File?Offset:?0x00000285//?(set)?Token:?0x06000004?RID:?4?RVA:?0x0000208D?File?Offset:?0x0000028Dpublic?string?Name?{?get;?set;?}
}
進一步使用 IL DASM 反匯編成中間語言 (IL) 代碼:
.property?instance?string?Name(){.get?instance?string?ConsoleApp1.Demo::get_Name().set?instance?void?modreq([System.Runtime]System.Runtime.CompilerServices.IsExternalInit)?ConsoleApp1.Demo::set_Name(string)}?//?end?of?property?Demo::Name
可以看到,屬性還是會編譯成get_XXX
、set_XXX
方法,只是 set_Name(string)
方法有一個 modreq
修飾符,并使用了 IsExternalInit
類型。
查看官方文檔[1],原來使用 modreq
特性是為了讓編譯器知道它會限制對屬性的訪問,更為關鍵的是,它不會在以下情況下受到保護:
成員反射
使用 dynamic
不識別 modreqs 的編譯器
實現
既然如此,我們可以使用反射在任何時候為 init-only
屬性賦值:
var?demo?=?new?Demo?{?Name?=?"Demo"?};typeof(Demo).GetProperty("Name").SetValue(demo,?"My?IO");?
Console.WriteLine(demo.Name);//輸出
My?IO
結論
今天,我們了解到,init
其實是 C# 編譯器的功能,在運行時還是可以修改其值的。
參考資料
[1]
官方文檔: https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/proposals/csharp-9.0/init
添加微信號【MyIO666】,邀你加入技術交流群