C#9.0:Init相关总结

背景

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

public class PersonInfo
 {
  /// <summary>
  /// 身份编号
  /// </summary>
  public string UserCode { get; }

  /// <summary>
  /// 姓名
  /// </summary>
  public string UserName { get; }

  /// <summary>
  /// 初始化赋值
  /// </summary>
  /// <param name="_userCode"></param>
  /// <param name="_userName"></param>
  public PersonInfo(string _userCode,string _userName)
  {
   UserCode = _userCode;
   UserName = _userName;
  }
 }

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

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

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

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

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

public class PersonInfo
 {
  /// <summary>
  /// 身份编号
  /// </summary>
  public string UserCode { get; set; }

  /// <summary>
  /// 姓名
  /// </summary>
  public string UserName { get; set; }
 }

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

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

public class PersonInfo
 {
  /// <summary>
  /// 身份编号
  /// </summary>
  public string? UserCode { get; init; }

  /// <summary>
  /// 姓名
  /// </summary>
  public string? 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只能在对象初始化的时候使用,第二次赋值就不被允许了。

即:一旦初始化完成之后,只初始化属性或索引就保护着对象的状态免于改变。

 var person = new PersonInfo() { UserCode="12345678", UserName="Brand" };
 //提示错误:只能在对象初始器或实例构造函数中分配 init-only
 person.UserName = "Brand1"; 

init属性访问器和只读字段

因为init访问器只能在初始化时被调用,所以在init属性访问器中可以改变封闭类的只读字段。

需要注意的是,从init访问器中来给readonly字段赋值仅限于跟init访问器处于同一类型中定义的字段,通过它是不能给父类中定义的readonly字段赋值的,关于这继承有关的示例,我们会在2.4类型间的层级传递中看到。

public class PersonInfo
 {
  private readonly string userCode = "<unknown>";
  private readonly string userName = "<unknown>";

  public string UserCode
  {
   get => userCode;
   init => userCode = (value ?? throw new ArgumentNullException(nameof(UserCode)));
  }
  public string UserName
  {
   get => userName;
   init => userName = (value ?? throw new ArgumentNullException(nameof(UserName)));
  }
 }

类型层级间的传递

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

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

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

public class PersonInfo
 {
  /// <summary>
  /// 身份编号
  /// </summary>
  public string UserCode { get; init; }

  /// <summary>
  /// 姓名
  /// </summary>
  public string UserName { get; init; }

  public PersonInfo()
  {
   UserCode = "1234567890";
   UserName = "Brand";
  }
 }

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

public class PersonInfoExt : PersonInfo
 {
  public PersonInfoExt()
  {
   UserCode = "1234567890_0";
   UserName = "Brand1";
  }
 }
var personext = new PersonInfoExt() { UserCode="1234567890_2", UserName="Brand2" };

从init访问器能被调用这一方面来看,对象实例在开放的构造阶段就可以被知晓。因此除过正常set可以做之外,init访问器的下列行为也是被允许的。

1、通过this或者base调用其他可用的init访问器

2、在同一类型中定义的readonly字段,是可以通过this给赋值的

init中是不能更改父类中的readonly字段的,只能更改本类中readonly字段。示例代码如下:

class PersonInfo1
 {
  protected readonly string UserCode_R;
  public String UserCode
  {
   get => UserCode_R;
   init => UserCode_R = value; // 正确:在同一类中定义的readonly属性,可以直接通过this给赋值的
  }
  internal String UserName { get; init; }
 }

 class PersonInfo1Ext : PersonInfo1
 {
  protected readonly int NewField;
  internal int NewProp
  {
   get => NewField;
   init
   {
    NewField = 100; // 正确
    UserCode = "123456";  // 正确
    UserCode_R = "1234567";  // 出错,试图修改基类中的readonly字段UserCode_R
   }
  }

  public PersonInfo1Ext()
  {
   UserCode = "123456"; // 正确
   UserCode_R = "1234567"; // 出错,试图修改基类中的readonly字段UserCode_R
  }
 }

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

public class PersonInfo
 {
  /// <summary>
  /// 身份编号
  /// </summary>
  public virtual string UserCode { get; init; }

  /// <summary>
  /// 姓名
  /// </summary>
  public virtual string UserName { get; set; }
 }

 public class PersonInfoExt1 : PersonInfo
 {
  public override string UserCode { get; init; }
  public override string UserName { get; set; }
 }

 public class PersonInfoExt2 : PersonInfo
 {
  // 错误: 基类的init属性必须由init来重写PersonInfo.UserCode
  public override int UserCode { get; set; }
  // 错误: 基类的init属性必须由set来重写PersonInfo.UserName
  public override string UserName { get; init; }
 }

init在接口接口中应用

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

interface IPersonInfo
 {
  string Usercode { get; init; }
  string UserName { get; init; }
 }

 class PersonInfo
 {
  void NewPersonInfo<T>() where T : IPersonInfo, new()
  {
   var person = new T()
   {
    Usercode = "1234567890",
    UserName = "Jerry"
   };
   person.Usercode = "111"; // 错误
  }
 }

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

readonly struct PersonInfo
 {
  /// <summary>
  /// 身份编号
  /// </summary>
  public string UserCode { get; init; }

  /// <summary>
  /// 姓名
  /// </summary>
  public string UserName { get; set; }
 }

但是要注意的是:

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

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

struct PersonInfo
 {
  /// <summary>
  /// 身份编号
  /// </summary>
  public readonly string UserCode { get; init; }

  /// <summary>
  /// 姓名
  /// </summary>
  public string UserName { get; readonly init; }
 }

以上就是C#9.0:Init相关总结的详细内容,更多关于C#9.0:Init的资料请关注我们其它相关文章!

(0)

相关推荐

  • C#9新特性init only setter的使用

    C# 9 中新支持了 init 关键字,这是一个特殊的 setter,用来指定只能在对象初始化的时候进行赋值,另外支持构造器简化的写法,比如:Target-typed new expression 在已知类型的情况下可以使用 new() 来代表构造方法的简化用法,可以简化字段的声明,也可以简化一次声明多个相同类型的变量 Sample 来看一个示例,我们定义一个测试用的 Person 类,测试代码如下: public class Person { public int Age { get; ini

  • 浅谈C#中的Infinity和NaN

    C#中double和float类型有两个特殊值: Infinity(无穷大):5.0 / 0.0 = Infinity NaN(not a number):0.0 / 0.0 = NaN 计算表达式 0.0 / 0.0 = NaN, NaN和Infinity可以在表达式中使用: 10 + Infinity = Infinity 10 + NaN = NaN Infinity * 0 = 0 NaN * 0 = NaN 以上这篇浅谈C#中的Infinity和NaN就是小编分享给大家的全部内容了,希

  • C# 9 中新加入的关键词 init,record,with

    一:背景 1. 讲故事 .NET5 终于在 2020-08-25 也就是大前天发布了第八个预览版,这么多的预览版搞得我都麻木了,接踵而来的就是更多的新特性加入到了 C# 9 中,既然还想呆在这条船上,得继续硬着头皮学习哈,这一篇跟大家聊聊新增的几个关键词. 二:新增关键词 1. init 出来一个新语法糖,首先要做的就是去揭它的老底,这样可以方便推测它的应用场景,为了方便表述,我先上一个例子: public class Person { public string Name { get; ini

  • C#9.0:Init相关总结

    背景 在以前的C#版本里面,如果需要定义一个不可修改的的类型的做法一般是:声明为readonly,并设置为只包含get访问器,不包含set访问器.如下: public class PersonInfo { /// <summary> /// 身份编号 /// </summary> public string UserCode { get; } /// <summary> /// 姓名 /// </summary> public string UserName

  • MySQL8.0内存相关参数总结

    MySQL理论上使用的内存 = 全局共享内存 + max_connections×线程独享内存. 也就是:innodb_buffer_pool_size + innodb_log_buffer_size + thread_cache_size +table_open_cache + table_definition_cache +key_buffer_size + max_connections *( thread_stack+ sort_buffer_size+join_buffer_size

  • Spring Boot 2.5.0 重新设计的spring.sql.init 配置有啥用

    弃用内容 先来纠正一个误区.主要之前在版本更新介绍的时候,存在一些表述上的问题.导致部分读者认为这次的更新是Datasource本身初始化的调整,但其实并不是.这次重新设计的只是对Datasource脚本初始化机制的重新设计. 先来看看这次被弃用部分的内容(位于org.springframework.boot.autoconfigure.jdbc.DataSourceProperties),如果你有用过这些配置内容,那么新配置就很容易理解了. /** * Mode to apply when d

  • vue.js中Vue-router 2.0基础实践教程

    前言 Vue.js的一大特色就是构建单页面应用十分方便,既然要方便构建单页面应用那么自然少不了路由,vue-router就是vue官方提供的一个路由框架.本文主要介绍了Vue-router 2.0的相关内容,分享出来供大家参考学习,下面来看看详细的介绍: 一.基础用法: <div id="app"> <h1>Hello App!</h1> <p> <!-- 使用 router-link 组件来导航. --> <!-- 通

  • setTimeout时间设置为0详细解析

    前言 本文主要给大家介绍了关于setTimeout时间设置为0的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 1.开胃菜,setTimeout为何物 首先看一下w3school上面对于setTimeout的解释 setTimeout(fn,millisec) 方法用于在指定的毫秒数后调用函数或计算表达式. 很简单,setTimeout() 只执行 fn 一次,到底什么时候执行取决于第二个参数millisec设定的毫秒数,所以很多人习惯上称之为延迟,无非就是延迟一段时

  • 在centos7安装zabbix3.0的超详细步骤记录

    前言 最近公司部分业务迁移机房,为了更方便的监控管理主机资源,决定上线zabbix监控平台.本文主要给大家介绍了关于centos7安装zabbix3.0的相关步骤,下面话不多说了,来一起看看详细的介绍吧 为什么要监控 在需要的时刻,提前提醒我们服务器出问题了 当出问题之后,可以找到问题的根源 网站/服务器 的可用性 安装前准备 1.0 系统时间同步在crontab中添加 #crontab -l 00 00 * * * /usr/sbin/ntpdate -u x.x.x.x #选择ntp服务器

  • Linux下卸载MySQL8.0版本的操作方法

    一.关闭MySQL [root@localhost /]# service mysqld stop Redirecting to /bin/systemctl stop mysqld.service 二.查看当前安装mysql情况,查找以前是否装有mysql [root@localhost /]# rpm -qa|grep -i mysql mysql-community-client-8.0.13-1.el7.x86_64 mysql-community-libs-8.0.13-1.el7.x

  • 通过history解决ajax不支持前进/后退/刷新的问题

    前言: 现在前后端基本都是通过ajax实现前后端接口数据的交互,但是,ajax有个小小的劣势,即:不支持浏览器"后退"和"前进"键. 但是,现在我们可以通过H5的histroy属性 解决ajax在交互请求的这个小bug. 事件描述: H5增加了一个事件window.onpopstate,当用户点击那两个按钮就会触 发这个事件.但是光检测到这个事件是不够的,还得能够传些参数,也就是说返回到之前那个页面的时候得知道那个页面的pageIndex.通过 history的pu

  • JS制作图形验证码实现代码

    第一步我们来到要展示验证码的页面,当我们按下营业执照的时候让其,弹出一个弹框,弹框的上面就是验证码,如图一所示: (图一) 弹框的样式如图二所示: (图二) 我们要对验证码的值进行校验,判断验证码是否输入正确,当输入不正确的时候,我们提示错误信息,提示信息如图三所示: (图三) 如果页面了验证正确,这不会提示错误信息并且调到我们的目标页面,如图四所示: (图四) 路由层描述 /** 供货商店铺-店铺简介 */ //1-在路由层进行设置,页面跳转到根目录下/buyer/vshop/info.ejs

  • 利用NodeJS和PhantomJS抓取网站页面信息以及网站截图

    利用PhantomJS做网页截图经济适用,但其API较少,做其他功能就比较吃力了.例如,其自带的Web Server Mongoose最高只能同时支持10个请求,指望他能独立成为一个服务是不怎么实际的.所以这里需要另一个语言来支撑服务,这里选用NodeJS来完成. 安装PhantomJS 首先,去PhantomJS官网下载对应平台的版本,或者下载源代码自行编译.然后将PhantomJS配置进环境变量,输入 $ phantomjs 如果有反应,那么就可以进行下一步了. 利用PhantomJS进行简

随机推荐