背景

在以前的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 } 

C#9.0:Init的更多相关文章

  1. Android的init过程:init.rc解析流程

    这几天打算看下安卓的代码,看优秀的源代码也是一种学习过程,看源代码的过程就感觉到,安卓确实是深受linux内核的影响,不少数据结构的使用方法全然一致.花了一中午时间,研究了下init.rc解析过程,做 ...

  2. imx6 Android6.0.1 init.rc解析

    1. 概述 1.1 概述 之前分析过android5的init.rc,不过还是不够仔细,现在来看看android6的,多的就不写了,只写关键点 忘记一些基本概念可以先看看之前的笔记: Android5 ...

  3. Linux:INIT runlevel service netstat ps top pgrep kill killall jobs pkill crontab

    INIT进程 Linux内核加载执行/sbin/init程序 -Linux的第一个进程,进程ID为1 -主配置文件:/etc/ininttab init 0 关机 init 1 单用户模式 init ...

  4. ERROR [RMI TCP Connection(3)-127.0.0.1] - init datasource error

    运行报错 ERROR [RMI TCP Connection(3)-127.0.0.1] - init datasource error, url: jdbc:mysql://localhost:33 ...

  5. Android安全升级的7.0: Nougat

    Tamic http://www.jianshu.com/users/3bbb1ddf4fd5/latest_articles 今年夏天以来,Google做了多种增强的安全性在Android的7.0N ...

  6. 【C#】datetimepicker里面如何设置日期为当天日期,而时间设为0:00或23:59?

    今天无意中发现要根据日期查询时间,datatimepicker控件会把时间默认成当前时间(当你的控件只显示日期时),这样查询出来的出来的数据会有误差,用来下面的办法成功设置日期为当天日期,而时间设为0 ...

  7. Java-MyBatis-3.0:MyBatis 3 简介

    ylbtech-Java-MyBatis-3.0:MyBatis 3 简介 1.返回顶部 1. 简介 什么是 MyBatis ? MyBatis 是一款优秀的持久层框架,它支持定制化 SQL.存储过程 ...

  8. 学习《人人都是产品经理2.0:写给泛产品经理》高清中文PDF+苏杰(作者)

    <人人都是产品经理2.0--写给泛产品经理>将从人开始,以人结束,中间说事,以一个产品从无到有的过程为框架--想清楚.做出来.推出去,外加一章综合案例.其中,最重要的想清楚.做出来.推出去 ...

  9. 一图了解 CODING 2.0:企业级持续交付解决方案

    近日,CODING 在 KubeCon 2019 上海站上正式推出了 DevOps 的一站式解决方案:CODING 2.0. CODING 2.0 进行了产品.产品理念.功能.首页的升级,对用户服务进 ...

  10. CODING 2.0:如何通过设计给品牌创造价值?

    升级背景 伴随着 CODING 理念的全面升级,CODING 正构建起覆盖构想到交付的全覆盖工具链,用户注册即可实践敏捷开发与 DevOps,提升软件交付质量与速度. 一直以来,CODING 作为软件 ...

随机推荐

  1. .NET 8 Video教程介绍(开篇)

    教程简介 本文将简单描述视频网站教程,视频网站是一个类似于腾讯视频一样的网站,视频资源用户自己上传,然后提供友好的界面查看视频和搜索视频,并且提供管理页面对于视频进行管理,我们将使用Blazor作为前 ...

  2. uni-app框架开发app发布流程

    uni-app框架开发app发布流程 1.首先公司申请软著 步骤:申请软著详细流程 - 阿长*长 - 博客园 (cnblogs.com) 一.安卓端 1,点击发行>原生-app云打包 正式包和自 ...

  3. Flask 使用Jinja2模板引擎

    Jinja2,由Flask框架的创作者开发,是一款功能丰富的模板引擎,以其完整的Unicode支持.灵活性.高效性和安全性而备受推崇.最初受Django模板引擎启发,Jinja2为Flask提供了强大 ...

  4. 旺店通·企业奇门和用友BIP接口打通对接实战

    旺店通·企业奇门和用友BIP接口打通对接实战 接通系统:旺店通·企业奇门 旺店通是北京掌上先机网络科技有限公司旗下品牌,国内的零售云服务提供商,基于云计算SaaS服务模式,以体系化解决方案,助力零售企 ...

  5. 【Javaweb】java中接口(interface)怎么用

    首先我们先了解什么是接口(interface) 实际情况中,又是我们必须从几个类中派生出一个子类,继承他们所有的属性和方法.但是,JAVA中是不支持多重继承的,那么为了满足这一目的,就有了接口,就可以 ...

  6. Mysql数据库插入数据时出现Unknown column ‘admin‘ in ‘field list‘错误

    报错内容 报错原因 字段和插入的值所用的引号不对 解决方案 insert into t_user(`username`,`password`,`email`) VALUES(`admin`,`admi ...

  7. AutoCAD ObjectARX 二次开发(2020版)--1,下载和部署开发环境--

    教程说明: 本教程为2019年10月开始编撰,使用CAD官方最新版本的软件和库.对旧版本仍有参考价值. 本教程中使用的各种软件版本为官方指定匹配版本. 本教程需要你拥有编程基础,对于普通编程常识不再敷 ...

  8. 10个PPT制作实用小技巧

    当制作PPT时,您可以使用一些实用的小技巧来提升演示效果和工作效率.以下是10个PPT制作实用小技巧的详细描述: 第一.选择合适的模板 选择合适的PPT模板非常重要,因为模板可以决定整个演示的风格和视 ...

  9. 利用Jdk动态代理模拟MyBatis的Mapper功能

    本文将先介绍jdk动态代理的基本用法,并对其原理和注意事项予以说明.之后将以两个最常见的应用场景为例,进行代码实操.这两个应用场景分别是拦截器和声明性接口,它们在许多开发框架中广泛使用.比如在spri ...

  10. 华企盾DSC导致导出文件报错常见处理方法

    1.导出文件的进程和打开该文件的进程启用OLE控制是否都是未勾选,以及启用虚拟重定向是否设置一致(要么都勾选要么都不勾) 2.用procmon监控个人模式下导出非加密的文件,搜索writefile的进 ...