浅谈C#9.0新特性之只读属性和记录

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

浅谈C#9.0新特性之只读属性和记录
⼤家好,这是 C# 9.0 新特性系列的第 4 篇⽂章。

熟悉函数式编程的童鞋⼀定对“只读”这个词不陌⽣。

为了保证代码块⾃⾝的“纯洁”,函数式编程是不能随便“弄脏”外来事物(参数、变量等)的,所以“只读”对函数式编程⾮常重要。

为了丰富 C# 对函数式编程⽀持,较新的 C# 版本引⼊了⼀些很有⽤的新特性。

⽐如 C# 8 中就对 struct 类型的⽅法增加了readonly 修饰符⽀持,被 readonly 修饰的⽅法是不能修改该⽅法所在类的属性的。

举个例⼦:
public struct FooValue
{
private int A { get; set; }
public readonly int IncreaseA()
{
A = A + 1; // 报错
return A;
}
}
⽽ C# 9 ⼜进⼀步增加了对“只读”的⽀持,此次增加了 init-only 属性和 record 相关特性,下⾯⼀⼀介绍。

Init-only 属性
我们知道类的属性有 set 和 get 两种访问器,现在 C# 9 增加⼀种属性访问器:init。

init 是 set 访问器的变体,它的作⽤是使属性只能在对象初始化的时候对其赋值,之后该属性就是只读的,因此叫 init-only 属性。

使⽤⽅式如下:
public class Foo
{
public string PropA { get; init; }
public string PropB { get; init; }
}
赋值操作:
var foo = new Foo { PropA = "A", PropB = "B" };
foo.PropA = "AA"; // 报错,PropA 此时是只读的!
由于 init 是在初始化阶段赋值,所以它可以在类内部修改 readonly 修饰的字段。

⽐如:
public class Foo
{
private readonly string propA;
private readonly string propB;
public string PropA
{
get => propA;
init => propA = (value ?? throw new ArgumentNullException(nameof(propA)));
}
public string PropA
{
get => propB;
init => propB = (value ?? throw new ArgumentNullException(nameof(propB)));
}
}
如果你知道在构造函数中可以对只读字段/属性赋值就⾃然也理解这⼀点。

记录 (Record)
做过财务系统的⼈都知道交易记录⼀旦⼊账是不能修改的,如果录⼊错误,就要新录⼊⼀笔负的记录把之前的红冲掉,再录⼊正确的记录。

应对类似这种只读记录的场景,C# 9 引⼊了 Record(记录,下⽂均使⽤中⽂的“记录”)的概念,它⽤来⽀持整个对象的只读特性(即实例化后为只读)。

使⽤⽅式如下:
public data class Foo
{
public string PropA { get; init; }
public string PropB { get; init; }
}
这⾥⽤了⼀个 data 关键字,表⽰该类的对象只是纯粹的记录值,它不是可修改的状态(在函数式编程中,所有的数据修改都
是状态在发⽣变化)。

上⾯的太⿇烦了,可以这样简写:
public data class Foo
{
string PropA;
string PropB;
}
默认属性都是 public 的,如果实在要改为 private,可以在属性定义前⾯加上 private 修饰符。

定位记录 (Positional Record)
有时候为了初始化更⽅便,可以定义构造函数来给属性赋值,初始化时只需要把属性值按顺序传给构造函数即可,这个操作称为定位构造(Positional Construction)。

同样,也可以使⽤解构函数(Deconstructor)来实现属性的解构,即按照解构函数的参数顺序从对象中提取属性的值,被称为定位解构(Positional Deconstructor)。

实现了定位构造或定位解构的记录称为定位记录(Positional Record)。

下⾯是⼀个定位记录的实现:
public data class Foo
{
string PropA;
string PropB;
public Foo(string propA, string propB)
=> (PropA, PropB) = (propA, propB);
public void Deconstruct(out string propA, out string propB)
=> (propA, propB) = (PropA, PropB);
}
这个写法太⿇烦了,可以直接简写为:
public data class Foo(string PropA, string PropB);
这样简短⼀句代码,其内部默认实现了 init-only ⾃动属性,且同时为所有属性定义了构造函数和解构函数。

使⽤⽰例:
var foo = new Foo("AA", "BB"); // 构造定位
var (a, b) = foo; // 解构定位
可以想象,记录的⼤部分使⽤场景,以上简写的写法能满⾜需求。

若有特殊场景,就不能简单,需要进⾏⾃定义修改其默认⾏为。

with 表达式
当处理不可变数据时,若要⽣成不同的状态,⼀个常见的场景是在⼀条旧记录基础上拷贝⼀条新的记录。

⽐如我们要修改 Foo 对象的 PropA 属性,我们就要拷贝该对象⽣成⼀个新的对象。

这个操作在函数式编程中被称为“⾮破坏性修改 (non-destructive mutation)”。

为了⽀持记录这个操作,C# 9 引⼊了 with 表达式,它可以很⽅便在⼀条原有记录基础上创建⼀条新记录。

⽰例:
var other = foo with { PropA = "AA" };
with 表达式内部其实是通过⼀个默认的 protected 构造函数来实现的,⼤致如下:
protected Foo(Foo original)
{
// 拷贝 original 的所有字段
}
如果默认实现的字段拷贝不符合你的需求,你也可以⼿动实现这个构造函数。

今天就分享到这⾥,敬请期待⼀下篇关于 C# 9 新特性的⽂章!
以上就是浅谈C# 9.0 新特性之只读属性和记录的详细内容,更多关于c# 特性之只读属性和记录的资料请关注其它相关⽂章!。

相关文档
最新文档