C#9.0:Init相关总结
背景
在以前的C#版本里面,如果需要定义一个不可修改的的类型的做法一般是:声明为readonly,并设置为只包含get访问器,不包含set访问器。如下:
publicclassPersonInfo
{
///
///身份编号
///
publicstringUserCode{get;}
///
///姓名
///
publicstringUserName{get;}
///
///初始化赋值
///
///
///
publicPersonInfo(string_userCode,string_userName)
{
UserCode=_userCode;
UserName=_userName;
}
}
这种方式是可行的,也达到我们的目的,但是代码量多,需要增加额外的构造方法来实现初始化赋值,并且如果字段越多,带参构造函数也会越大,开发工作量也越大,更不好维护。
为了改变这种状态,C#9.0提供了一种解决方案:在对象初始换的时候就配置为只读的方式。
特别对一口气创建含有嵌套结构的树状对象来说更有用。下面是一个用户信息初始化的案例:
PersonInfopi=newPersonInfo(){UserCode="1234567890",UserName="Brand"};
从这个例子说明了,要进行对象初始化,我们必须先要在需要初始化的属性中添加set访问器,然后才能在对象初始化器中通过给属性或者索引器赋值来实现。如下:
publicclassPersonInfo
{
///
///身份编号
///
publicstringUserCode{get;set;}
///
///姓名
///
publicstringUserName{get;set;}
}
所以对于初始化来说,属性必须是可变的,set访问器就必须存在。这就是问题所在,很多情况下为了避免属性初始化之后再被改变,就需要不可变对象类型,因此setter访问器在这里明显不适用。
基于这种有这种常见的需要和局限性,C#9.0引入了只用来初始化的init设置访问器。这时,上面的PersonInfo类就可以定义成下面的样子:
publicclassPersonInfo
{
///
///身份编号
///
publicstring?UserCode{get;init;}
///
///姓名
///
publicstring?UserName{get;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只能在对象初始化的时候使用,第二次赋值就不被允许了。
即:一旦初始化完成之后,只初始化属性或索引就保护着对象的状态免于改变。
varperson=newPersonInfo(){UserCode="12345678",UserName="Brand"};
//提示错误:只能在对象初始器或实例构造函数中分配init-only
person.UserName="Brand1";
init属性访问器和只读字段
因为init访问器只能在初始化时被调用,所以在init属性访问器中可以改变封闭类的只读字段。
需要注意的是,从init访问器中来给readonly字段赋值仅限于跟init访问器处于同一类型中定义的字段,通过它是不能给父类中定义的readonly字段赋值的,关于这继承有关的示例,我们会在2.4类型间的层级传递中看到。
publicclassPersonInfo
{
privatereadonlystringuserCode="";
privatereadonlystringuserName="";
publicstringUserCode
{
get=>userCode;
init=>userCode=(value??thrownewArgumentNullException(nameof(UserCode)));
}
publicstringUserName
{
get=>userName;
init=>userName=(value??thrownewArgumentNullException(nameof(UserName)));
}
}
类型层级间的传递
我们知道只包含get访问器的属性或索引器只能在所处类的自身构造函数中被初始化,但init访问器可以进行设置的规则是可以跨类型层级传递的。
带有init访问器的成员只要是可访问的,对象实例并能在构造阶段被知晓,那这个成员就是可设置的。
1、在对象初始化中使用,是允许的
publicclassPersonInfo
{
///
///身份编号
///
publicstringUserCode{get;init;}
///
///姓名
///
publicstringUserName{get;init;}
publicPersonInfo()
{
UserCode="1234567890";
UserName="Brand";
}
}
2、在派生类的实例构造函数中,也是允许的,如下面这两个例子:
publicclassPersonInfoExt:PersonInfo
{
publicPersonInfoExt()
{
UserCode="1234567890_0";
UserName="Brand1";
}
}
varpersonext=newPersonInfoExt(){UserCode="1234567890_2",UserName="Brand2"};
从init访问器能被调用这一方面来看,对象实例在开放的构造阶段就可以被知晓。因此除过正常set可以做之外,init访问器的下列行为也是被允许的。
1、通过this或者base调用其他可用的init访问器
2、在同一类型中定义的readonly字段,是可以通过this给赋值的
init中是不能更改父类中的readonly字段的,只能更改本类中readonly字段。示例代码如下:
classPersonInfo1
{
protectedreadonlystringUserCode_R;
publicStringUserCode
{
get=>UserCode_R;
init=>UserCode_R=value;//正确:在同一类中定义的readonly属性,可以直接通过this给赋值的
}
internalStringUserName{get;init;}
}
classPersonInfo1Ext:PersonInfo1
{
protectedreadonlyintNewField;
internalintNewProp
{
get=>NewField;
init
{
NewField=100;//正确
UserCode="123456";//正确
UserCode_R="1234567";//出错,试图修改基类中的readonly字段UserCode_R
}
}
publicPersonInfo1Ext()
{
UserCode="123456";//正确
UserCode_R="1234567";//出错,试图修改基类中的readonly字段UserCode_R
}
}
如果init被用于virtual修饰的属性或者索引器,那么所有的覆盖重写都必须被标记为init,是不能用set的。同样地,我们不可能用init来覆盖重写一个set的。
publicclassPersonInfo
{
///
///身份编号
///
publicvirtualstringUserCode{get;init;}
///
///姓名
///
publicvirtualstringUserName{get;set;}
}
publicclassPersonInfoExt1:PersonInfo
{
publicoverridestringUserCode{get;init;}
publicoverridestringUserName{get;set;}
}
publicclassPersonInfoExt2:PersonInfo
{
//错误:基类的init属性必须由init来重写PersonInfo.UserCode
publicoverrideintUserCode{get;set;}
//错误:基类的init属性必须由set来重写PersonInfo.UserName
publicoverridestringUserName{get;init;}
}
init在接口接口中应用
一个接口中的默认实现,也是可以采用init进行初始化,下面就是一个应用模式示例。
interfaceIPersonInfo
{
stringUsercode{get;init;}
stringUserName{get;init;}
}
classPersonInfo
{
voidNewPersonInfo()whereT:IPersonInfo,new()
{
varperson=newT()
{
Usercode="1234567890",
UserName="Jerry"
};
person.Usercode="111";//错误
}
}
init访问器是允许在readonlystruct中的属性中使用的,init和readonly的目标都是一致的,就是只读。示例代码如下:
readonlystructPersonInfo
{
///
///身份编号
///
publicstringUserCode{get;init;}
///
///姓名
///
publicstringUserName{get;set;}
}
但是要注意的是:
1、不管是readonly结构还是非readonly结构,不管是手工定义属性还是自动生成属性,init都是可以使用的。
2、init访问器本身是不能标记为readonly的。但是所在属性或索引器可以被标记为readonly
structPersonInfo
{
///
///身份编号
///
publicreadonlystringUserCode{get;init;}
///
///姓名
///
publicstringUserName{get;readonlyinit;}
}
以上就是C#9.0:Init相关总结的详细内容,更多关于C#9.0:Init的资料请关注毛票票其它相关文章!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。