前言
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】,邀你加入技术交流群