zoukankan      html  css  js  c++  java
  • C#9.0:Init

    背景

    在以前的C#版本里面,如果需要定义一个不可修改的的类型的做法一般是:声明为readonly,并设置为只包含get访问器,不包含set访问器。如下:

     1  public class PersonInfo
     2     {
     3         /// <summary>
     4         /// 身份编号
     5         /// </summary>
     6         public string UserCode { get; }
     7 
     8         /// <summary>
     9         /// 姓名
    10         /// </summary>
    11         public string UserName { get; }
    12 
    13         /// <summary>
    14         /// 初始化赋值
    15         /// </summary>
    16         /// <param name="_userCode"></param>
    17         /// <param name="_userName"></param>
    18         public PersonInfo(string _userCode,string _userName)
    19         {
    20             UserCode = _userCode;
    21             UserName = _userName;
    22         }
    23

    这种方式是可行的,也达到我们的目的,但是代码量多,需要增加额外的构造方法来实现初始化赋值,并且如果字段越多,带参构造函数也会越大,开发工作量也越大,更不好维护。

    为了改变这种状态,C#9.0提供了一种解决方案:在对象初始换的时候就配置为只读的方式。

    特别对一口气创建含有嵌套结构的树状对象来说更有用。下面是一个用户信息初始化的案例:

    1 PersonInfo pi = new PersonInfo() { UserCode="1234567890", UserName="Brand" }; 

    从这个例子说明了,要进行对象初始化,我们必须先要在需要初始化的属性中添加set访问器,然后才能在对象初始化器中通过给属性或者索引器赋值来实现。如下:

     1   public class PersonInfo
     2     {
     3         /// <summary>
     4         /// 身份编号
     5         /// </summary>
     6         public string UserCode { get; set; }
     7 
     8         /// <summary>
     9         /// 姓名
    10         /// </summary>
    11         public string UserName { get; set; }
    12

    所以对于初始化来说,属性必须是可变的,set访问器就必须存在。这就是问题所在,很多情况下为了避免属性初始化之后再被改变,就需要不可变对象类型,因此setter访问器在这里明显不适用。

    基于这种有这种常见的需要和局限性,C#9.0引入了只用来初始化的init设置访问器。这时,上面的PersonInfo类就可以定义成下面的样子:

     1   public class PersonInfo
     2     {
     3         /// <summary>
     4         /// 身份编号
     5         /// </summary>
     6         public string? UserCode { get; init; }
     7 
     8         /// <summary>
     9         /// 姓名
    10         /// </summary>
    11         public string? UserName { get; init; }
    12

    这边通过采用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
     2     {
     3         private readonly string userCode = "<unknown>";
     4         private readonly string userName = "<unknown>";
     5 
     6         public string UserCode
     7         {
     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         }      
    16     }

    类型层级间的传递

    我们知道只包含get访问器的属性或索引器只能在所处类的自身构造函数中被初始化,但init访问器可以进行设置的规则是可以跨类型层级传递的。

    带有init访问器的成员只要是可访问的,对象实例并能在构造阶段被知晓,那这个成员就是可设置的。

    1、在对象初始化中使用,是允许的

     1     public class PersonInfo
     2     {
     3         /// <summary>
     4         /// 身份编号
     5         /// </summary>
     6         public string UserCode { get; init; }
     7 
     8         /// <summary>
     9         /// 姓名
    10         /// </summary>
    11         public string UserName { get; init; }
    12 
    13         public PersonInfo()
    14         {
    15             UserCode = "1234567890";
    16             UserName = "Brand";
    17         }
    18

    2、在派生类的实例构造函数中,也是允许的,如下面这两个例子:

    1     public class PersonInfoExt : PersonInfo
    2     {
    3         public PersonInfoExt()
    4         {
    5             UserCode = "1234567890_0";
    6             UserName = "Brand1";
    7         }
    8     }
    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
     2     {
     3         protected readonly string UserCode_R;
     4         public String UserCode
     5         {
     6             get => UserCode_R;
     7             init => UserCode_R = value; // 正确:在同一类中定义的readonly属性,可以直接通过this给赋值的
     8         }
     9         internal String UserName { get; init; }
    10     }
    11 
    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         }
    25 
    26         public PersonInfo1Ext()
    27         {
    28             UserCode = "123456";    // 正确 
    29             UserCode_R = "1234567";  // 出错,试图修改基类中的readonly字段UserCode_R
    30         }
    31     }

    如果init被用于virtual修饰的属性或者索引器,那么所有的覆盖重写都必须被标记为init,是不能用set的。同样地,我们不可能用init来覆盖重写一个set的。

     1 public class PersonInfo
     2     {
     3         /// <summary>
     4         /// 身份编号
     5         /// </summary>
     6         public virtual string UserCode { get; init; }
     7 
     8         /// <summary>
     9         /// 姓名
    10         /// </summary>
    11         public virtual string UserName { get; set; }
    12     }
    13 
    14     public class PersonInfoExt1 : PersonInfo
    15     {
    16         public override string UserCode { get; init; }
    17         public override string UserName { get; set; }
    18     }
    19 
    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; }
    26

    init在接口接口中应用

    一个接口中的默认实现,也是可以采用init进行初始化,下面就是一个应用模式示例。

     1  interface IPersonInfo
     2     {
     3         string Usercode { get; init; }
     4         string UserName { get; init; }
     5     }
     6 
     7     class PersonInfo
     8     {
     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         }
    18

    init访问器是允许在readonly struct中的属性中使用的,init和readonly的目标都是一致的,就是只读。示例代码如下:

     1    readonly struct PersonInfo
     2     {
     3         /// <summary>
     4         /// 身份编号
     5         /// </summary>
     6         public string UserCode { get; init; }
     7 
     8         /// <summary>
     9         /// 姓名
    10         /// </summary>
    11         public string UserName { get; set; }
    12

    但是要注意的是:

    1、不管是readonly结构还是非readonly结构,不管是手工定义属性还是自动生成属性,init都是可以使用的。

    2、init访问器本身是不能标记为readonly的。但是所在属性或索引器可以被标记为readonly

     1     struct PersonInfo
     2     {
     3         /// <summary>
     4         /// 身份编号
     5         /// </summary>
     6         public readonly string UserCode { get; init; }
     7 
     8         /// <summary>
     9         /// 姓名
    10         /// </summary>
    11         public string UserName { get; readonly init; }
    12
    码字不易,欢迎关注,欢迎转载
    作者:翁智华
    本文采用「CC BY 4.0」知识共享协议进行许可,转载请注明作者及出处。
  • 相关阅读:
    js-添加删除记录-修改
    js-添加删除记录-添加
    js-添加删除记录-删除
    dom增删改
    事件的冒泡
    div随鼠标在浏览器的窗口任意移动
    多选框全选练习
    python-day3
    python-day2
    python-day1
  • 原文地址:https://www.cnblogs.com/wzh2010/p/14093925.html
Copyright © 2011-2022 走看看