; init; }
这边通过采用init访问器,代码变得简洁易懂了,满足了上面的只读需求,而且更易编码和维护。
定义和使用
init(只初始化属性或索引器访问器):只在对象构造阶段进行初始化时可以用来赋值,算是set访问器的变体,set访问器的位置使用init来替换。init有着如下限制:
1、init访问器只能用在实例属性或索引器中,静态属性或索引器中不可用。
2、属性或索引器不能同时包含init和set两个访问器
3、如果基类的属性有init,那么属性或索引器的所有相关重写,都必须有init。接口也一样。
什么时候设置init访问器
除过在局部方法和lambda表达式中,带有init访问器的属性和索引器可以在下面几种情况中可设置的。这几个设置的时机都是在对象的构造阶段。过了构造阶段,后续赋值操作就不允许了。
1、在对象初始化器工作期间
2、在with表达式初始化器工作期间
3、在所处或者派生的类型的实例构造函数中,在this或者base使用上
4、在任意属性init访问器里面,在this或者base使用上
5、在带有命名参数的attribute使用中
在这些限制条件下,意味着我们上面定义的PersonInfo只能在对象初始化的时候使用,第二次赋值就不被允许了。
即:一旦初始化完成之后,只初始化属性或索引就保护着对象的状态免于改变。
1 var person = new PersonInfo() { UserCode="12345678", UserName="Brand" };
2 //提示错误:只能在对象初始器或实例构造函数中分配 init-only
3 person.UserName = "Brand1";
init属性访问器和只读字段
因为init访问器只能在初始化时被调用,所以在init属性访问器中可以改变封闭类的只读字段。
需要注意的是,从init访问器中来给readonly字段赋值仅限于跟init访问器处于同一类型中定义的字段,通过它是不能给父类中定义的readonly字段赋值的,关于这继承有关的示例,我们会在2.4类型间的层级传递中看到。
1 public class PersonInfo
3 private readonly string userCode = "<unknown>";
4 private readonly string userName = "<unknown>";
6 public string UserCode
8 get => userCode;
9 init => userCode = (value ?? throw new ArgumentNullException(nameof(UserCode)));
10 }
11 public string UserName
12 {
13 get => userName;
14 init => userName = (value ?? throw new ArgumentNullException(nameof(UserName)));
15 }
类型层级间的传递
我们知道只包含get访问器的属性或索引器只能在所处类的自身构造函数中被初始化,但init访问器可以进行设置的规则是可以跨类型层级传递的。
带有init访问器的成员只要是可访问的,对象实例并能在构造阶段被知晓,那这个成员就是可设置的。
1、在对象初始化中使用,是允许的
1 public class PersonInfo
3 /// <summary>
4 /// 身份编号
5 /// </summary>
6 public string UserCode { get; init; }
8 /// <summary>
9 /// 姓名
10 /// </summary>
11 public string UserName { get; init; }
13 public PersonInfo()
14 {
15 UserCode = "1234567890";
16 UserName = "Brand";
17 }
2、在派生类的实例构造函数中,也是允许的,如下面这两个例子:
1 public class PersonInfoExt : PersonInfo
3 public PersonInfoExt()
5 UserCode = "1234567890_0";
6 UserName = "Brand1";
1 var personext = new PersonInfoExt() { UserCode="1234567890_2", UserName="Brand2" };
从init访问器能被调用这一方面来看,对象实例在开放的构造阶段就可以被知晓。因此除过正常set可以做之外,init访问器的下列行为也是被允许的。
1、通过this或者base调用其他可用的init访问器
2、在同一类型中定义的readonly字段,是可以通过this给赋值的
init中是不能更改父类中的readonly字段的,只能更改本类中readonly字段。示例代码如下:
1 class PersonInfo1
3 protected readonly string UserCode_R;
4 public String UserCode
6 get => UserCode_R;
7 init => UserCode_R = value; // 正确:在同一类中定义的readonly属性,可以直接通过this给赋值的
9 internal String UserName { get; init; }
10 }
12 class PersonInfo1Ext : PersonInfo1
13 {
14 protected readonly int NewField;
15 internal int NewProp
16 {
17 get => NewField;
18 init
19 {
20 NewField = 100; // 正确
21 UserCode = "123456"; // 正确
22 UserCode_R = "1234567"; // 出错,试图修改基类中的readonly字段UserCode_R
23 }
24 }
26 public PersonInfo1Ext()
27 {
28 UserCode = "123456"; // 正确
29 UserCode_R = "1234567"; // 出错,试图修改基类中的readonly字段UserCode_R
30 }
如果init被用于virtual修饰的属性或者索引器,那么所有的覆盖重写都必须被标记为init,是不能用set的。同样地,我们不可能用init来覆盖重写一个set的。
1 public class PersonInfo
3 /// <summary>
4 /// 身份编号
5 /// </summary>
6 public virtual string UserCode { get; init; }
8 /// <summary>
9 /// 姓名
10 /// </summary>
11 public virtual string UserName { get; set; }
12 }
14 public class PersonInfoExt1 : PersonInfo
15 {
16 public override string UserCode { get; init; }
17 public override string UserName { get; set; }
18 }
20 public class PersonInfoExt2 : PersonInfo
21 {
22 // 错误: 基类的init属性必须由init来重写PersonInfo.UserCode
23 public override int UserCode { get; set; }
24 // 错误: 基类的init属性必须由set来重写PersonInfo.UserName
25 public override string UserName { get; init; }
init在接口接口中应用
一个接口中的默认实现,也是可以采用init进行初始化,下面就是一个应用模式示例。
1 interface IPersonInfo
3 string Usercode { get; init; }
4 string UserName { get; init; }
7 class PersonInfo
9 void NewPersonInfo<T>() where T : IPersonInfo, new()
10 {
11 var person = new T()
12 {
13 Usercode = "1234567890",
14 UserName = "Jerry"
15 };
16 person.Usercode = "111"; // 错误
17 }
init访问器是允许在readonly struct中的属性中使用的,init和readonly的目标都是一致的,就是只读。示例代码如下:
1 readonly struct PersonInfo
3 /// <summary>
4 /// 身份编号
5 /// </summary>
6 public string UserCode { get; init; }
8 /// <summary>
9 /// 姓名
10 /// </summary>
11 public string UserName { get; set; }
但是要注意的是:
1、不管是readonly结构还是非readonly结构,不管是手工定义属性还是自动生成属性,init都是可以使用的。
2、init访问器本身是不能标记为readonly的。但是所在属性或索引器可以被标记为readonly
1 struct PersonInfo
3 /// <summary>
4 /// 身份编号
5 /// </summary>
6 public readonly string UserCode { get; init; }
8 /// <summary>
9 /// 姓名
10 /// </summary>
11 public string UserName { get; readonly init; }