C# 9.0新特性——只初始化设置器
作者:未来的邀请 发布时间:2023-03-19 02:31:45
自C#1.0版本以来,我们要定义一个不可变数据类型的基本做法就是:先声明字段为readonly,再声明只包含get访问器的属性。例子如下:
1、背景与动机
自C#1.0版本以来,我们要定义一个不可变数据类型的基本做法就是:先声明字段为readonly,再声明只包含get访问器的属性。例子如下:
struct Point
{
public int X { get; }
public int Y { get; }
public Point(int x, int y)
{
this.X = x;
this.Y = y;
}
}
这种方式虽然很有效,但是它是以添加大量代码为代价的,并且类型越大,属性就越多,工作量就大,也就意味着更低的生产效率。
为了节省工作量,我们也用对象初始化方式来解决。对于创建对象来说,对象初始化方式是一种非常灵活和可读的方式,特别对一口气创建含有嵌套结构的树状对象来说更有用。下面是一个简单的对象初始化的例子:
var person = new Person{ FirstName = "Mads", LastName = "Torgersen" };
从这个例子,可以看出,要进行对象初始化,我们不得不先要在需要初始化的属性中添加set访问器,然后在对象初始化器中,通过给属性或者索引器赋值来实现。
public class Person
{
public string? FirstName { get; set; }
public string? LastName { get; set; }
}
这种方式最大的局限就是,对于初始化来说,属性必须是可变的,也就是说,set访问器对于初始化来说是必须的,而其他情况下又不需要set,而且我们需要的是不可变对象类型,因此这个setter明显在这里就不合适。既然有这种常见的需要和局限性,那么我为何不引入一个只能用来初始化的Setter呢?于是只用来初始化的init设置访问器就出现了。这时,上面的Point结构定义就可以简化成下面的样子:
struct Point
{
public int X { get; init; }
public int Y { get; init; }
}
那么现在,我们使用对象初始化器来创建一个实例:
var p = new Point() { X = 54, Y = 717 };
第二例子Person类型中,将set访问器换为init就成了不可变类型了。同时,使用对象初始化器方式没有变化,依然如前面所写。
public class Person
{
public string? FirstName { get; init; }
public string? LastName { get; init; }
}
通过采用init访问器,编码量减少,满足了只读需求,代码简洁易懂。
2. 定义和要求
只初始化属性或索引器访问器是一个只在对象构造阶段进行初始化时用来赋值的set访问器的变体,它通过在set访问器的位置来使用init来实现的。init有着如下限制:
init访问器只能用在实例属性或索引器中,静态属性或索引器中不可用。
属性或索引器不能同时包含init和set两个访问器
如果基类的属性有init,那么属性或索引器的所有相关重写,都必须有init。接口也一样。
2.1 init访问器可设置值的时机
除过在局部方法和lambda表达式中,带有init访问器的属性和索引器在下面情况是被认为可设置的。这几个可以进行设置的时机,在这里统称为对象的构造阶段。除过这个构造阶段之外,其他的后续赋值操作是不允许的。
在对象初始化器工作期间
在with表达式初始化器工作期间
在所处或者派生的类型的实例构造函数中,在this或者base使用上
在任意属性init访问器里面,在this或者base使用上
在带有命名参数的attribute使用中
根据这些时机,这意味着Person类可以按如下方式使用。在下面代码中第一行初始化赋值正确,第二行再次赋值就不被允许了。这说明,一旦初始化完成之后,只初始化属性或索引就保护着对象的状态免于改变。
var person = new Person { FirstName = "Mads", LastName = "Nielsen" }; // OK
person.LastName = "Torgersen"; // 错误!
2.2 init属性访问器和只读字段
因为init访问器只能在初始化时被调用,所以在init属性访问器中可以改变封闭类的只读字段。需要注意的是,从init访问器中来给readonly字段赋值仅限于跟init访问器处于同一类型中定义的字段,通过它是不能给父类中定义的readonly字段赋值的,关于这继承有关的示例,我们会在2.4类型间的层级传递中看到。
public class Person
{
private readonly string firstName = "<unknown>";
private readonly string lastName = "<unknown>";
public string FirstName
{
get => firstName;
init => firstName = (value ?? throw new ArgumentNullException(nameof(FirstName)));
}
public string LastName
{
get => lastName;
init => lastName = (value ?? throw new ArgumentNullException(nameof(LastName)));
}
}
2.3 类型层级间的传递
我们知道只包含get访问器的属性或索引器只能在所处类的自身构造函数中被初始化,但init访问器可以进行设置的规则是可以跨类型层级传递的。带有init访问器的成员只要是可访问的,对象实例并能在构造阶段被知晓,那这个成员就是可设置的。
class Person
{
public Person()
{
//下面这段都是允许的
Name = "Unknown";
Age = 0;
}
public string Name { get; init; }
public int Age { get; }
}
class Adult : Person
{
public Adult()
{
// 只有get访问器的属性会出错,但是带有init是允许的
Name = "Unknown Adult"; //正确
Age = 18; //错误
}
}
class Consumption
{
void Example()
{
var d = new Adult()
{
Name = "Jack", //正确
Age = 23 //错误,因为是只读,只有get
};
}
}
从init访问器能被调用这一方面来看,对象实例在开放的构造阶段就可以被知晓。因此除过正常set可以做之外,init访问器的下列行为也是被允许的。
通过this或者base调用其他可用的init访问器
在同一类型中定义的readonly字段,是可以通过this给赋值的
class Example
{
public Example()
{
Prop1 = 1;
}
readonly int Field1;
string Field2;
int Prop1 { get; init; }
public bool Prop2
{
get => false;
init
{
Field1 = 500; // 正确
Field2 = "hello"; // 正确
Prop1 = 50; // 正确
}
}
}
前面2.2节中提到,init中是不能更改父类中的readonly字段的,只能更改本类中readonly字段。示例代码如下:
class BaseClass
{
protected readonly int Field;
public int Prop
{
get => Field;
init => Field = value; // 正确
}
internal int OtherProp { get; init; }
}
class DerivedClass : BaseClass
{
protected readonly int DerivedField;
internal int DerivedProp
{
get => DerivedField;
init
{
DerivedField = 89; // 正确
Prop = 0; // 正确
Field = 35; // 出错,试图修改基类中的readonly字段Field
}
}
public DerivedClass()
{
Prop = 23; // 正确
Field = 45; // 出错,试图修改基类中的readonly字段Field
}
}
如果init被用于virtual修饰的属性或者索引器,那么所有的覆盖重写都必须被标记为init,是不能用set的。同样地,我们不可能用init来覆盖重写一个set的。
class Person
{
public virtual int Age { get; init; }
public virtual string Name { get; set; }
}
class Adult : Person
{
public override int Age { get; init; }
public override string Name { get; set; }
}
class Minor : Person
{
// 错误: 属性必须有init来重写Person.Age
public override int Age { get; set; }
// 错误: 属性必须有set来重写Person.Name
public override string Name { get; init; }
}
2.4 init和接口
一个接口中的默认实现,也是可以采用init进行初始化,下面就是一个应用模式示例。
interface IPerson
{
string Name { get; init; }
}
class Initializer
{
void NewPerson<T>() where T : IPerson, new()
{
var person = new T()
{
Name = "Jerry"
};
person.Name = "Jerry"; // 错误
}
}
2.5 init和readonly struct
init访问器是允许在readonly struct中的属性中使用的,init和readonly的目标都是一致的,就是只读。示例代码如下:
readonly struct Point
{
public int X { get; init; }
public int Y { get; init; }
}
但是要注意的是:
不管是readonly结构还是非readonly结构,不管是手工定义属性还是自动生成属性,init都是可以使用的。
init访问器本身是不能标记为readonly的。但是所在属性或索引器可以被标记为readonly
struct Point
{
public readonly int X { get; init; } // 正确
public int Y { get; readonly init; } // 错误
}
作者:MarkKang
出处:https://www.cnblogs.com/markkang/
来源:https://www.cnblogs.com/markkang/p/14011089.html


猜你喜欢
- 标准函数标准函数就是在Standard.kt文件中定义的函数,任何Kotlin代码都可以自由地调用所有的标准函数let函数就属于是一个标准函
- 前些日子有一个项目需要从word文件中取表格数据并进行处理,网上大部分方案都是基于office的com组件实现,但是这样有一个缺点,如果电脑
- Feign调用服务Headers传参在使用springcloud中经常会出现个服务调用,一般情况下会在Headers加上token的验证,那
- 本文实例为大家分享了C#实现上传下载图片的具体代码,供大家参考,具体内容如下1.首先我们通过流来上传下载图片,所有操作只停留在流这一层Mem
- 二维码是什么二维码 QR Code,全称为:Quick Response Code,最早用于日本汽车制造业追踪零部件。QR现有40个标准版本
- 绝大部分知识与实例来自O'REILLY的《Java网络编程》(Java Network Programming,Fourth Edi
- 1.问题分析我们在开发中经常遇到多个实体类有共同的属性字段,例如在用户注册时需要设置创建时间、创建人、修改时间、修改人等字段,在用户编辑信息
- 需求:将 一个容器List<Bean> 按照一定的字段进行分组,分组过后的值为特定的BEAN 里面的属性例如:假定有这样一个Be
- 前言菱形继承是多重继承中跑不掉的,Java拿掉了多重继承,辅之以接口。C++中虽然没有明确说明接口这种东西,但是只有纯虚函数的类可以看作Ja
- 创建一个脚本,输入下面的代码。脚本不需要添加到任何物体上,globle变量可以跨场景全局调用。using System.Collection
- 前言工作中是否遇到这样的场景?1、需要异步线程执行,而且需要获取到线程执行返回的结果。2、如果执行过程异常,可以按照自定义方式消费异常信息。
- #region 提示信息#endregion作用:折叠并隐藏代码 ,别且折叠以后能够显示白字“提示信息”如下图就是使用了#region和#e
- 一、访问或添加request/session/application属性public String scope() throws Excep
- 在日常开发过程中时常需要用到设计模式,但是设计模式有23种,如何将这些设计模式了然于胸并且能在实际开发过程中应用得得心应手呢?和我一起跟着《
- 一、引言在前面的文章中,我们是使用“锁”的方式实现了线程间的通信,这种通信方式比较笨重。除了锁之外,
- 业务场景我们知道在使用PageHelper分页插件时,会对执行PageHelper.startPage(pageNum, pageSize)
- 在后端数据接口项目开发中,经常遇到返回的数据中有null值,导致前端需要进行判断处理,否则容易出现undefined的情况,如何便捷的将nu
- 免责声明:本教程所有资源均来源于网络;仅用于学习交流,请勿用于任何商业行为;如需要,请使用正版授权;侵权联删。推荐最新 IntelliJ I
- 一、项目背景1、介绍:最近在springboot项目中需要做一个阿里云OSS图片上传功能点,将OSS图片上传代码提取到公共工具类中,为了方便
- 本文实例讲述了Android开发实现的ViewPager引导页功能(动态加载指示器)。分享给大家供大家参考,具体如下:先看效果图咯~现在几乎