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

 更新时间:2020年11月3日 15:20  点击:1857

大家好,这是 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# 特性之只读属性和记录的资料请关注猪先飞其它相关文章!

[!--infotagslink--]

相关文章

  • C#实现简单的登录界面

    我们在使用C#做项目的时候,基本上都需要制作登录界面,那么今天我们就来一步步看看,如果简单的实现登录界面呢,本文给出2个例子,由简入难,希望大家能够喜欢。...2020-06-25
  • 浅谈C# 字段和属性

    这篇文章主要介绍了C# 字段和属性的的相关资料,文中示例代码非常详细,供大家参考和学习,感兴趣的朋友可以了解下...2020-11-03
  • C#中截取字符串的的基本方法详解

    这篇文章主要介绍了C#中截取字符串的的基本方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-11-03
  • C#连接SQL数据库和查询数据功能的操作技巧

    本文给大家分享C#连接SQL数据库和查询数据功能的操作技巧,本文通过图文并茂的形式给大家介绍的非常详细,需要的朋友参考下吧...2021-05-17
  • C#实现简单的Http请求实例

    这篇文章主要介绍了C#实现简单的Http请求的方法,以实例形式较为详细的分析了C#实现Http请求的具体方法,需要的朋友可以参考下...2020-06-25
  • C#中new的几种用法详解

    本文主要介绍了C#中new的几种用法,具有很好的参考价值,下面跟着小编一起来看下吧...2020-06-25
  • 使用Visual Studio2019创建C#项目(窗体应用程序、控制台应用程序、Web应用程序)

    这篇文章主要介绍了使用Visual Studio2019创建C#项目(窗体应用程序、控制台应用程序、Web应用程序),小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2020-06-25
  • C#开发Windows窗体应用程序的简单操作步骤

    这篇文章主要介绍了C#开发Windows窗体应用程序的简单操作步骤,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-04-12
  • C#从数据库读取图片并保存的两种方法

    这篇文章主要介绍了C#从数据库读取图片并保存的方法,帮助大家更好的理解和使用c#,感兴趣的朋友可以了解下...2021-01-16
  • C#和JavaScript实现交互的方法

    最近做一个小项目不可避免的需要前端脚本与后台进行交互。由于是在asp.net中实现,故问题演化成asp.net中jiavascript与后台c#如何进行交互。...2020-06-25
  • 经典实例讲解C#递归算法

    这篇文章主要用实例讲解C#递归算法的概念以及用法,文中代码非常详细,帮助大家更好的参考和学习,感兴趣的朋友可以了解下...2020-06-25
  • C++调用C#的DLL程序实现方法

    本文通过例子,讲述了C++调用C#的DLL程序的方法,作出了以下总结,下面就让我们一起来学习吧。...2020-06-25
  • 轻松学习C#的基础入门

    轻松学习C#的基础入门,了解C#最基本的知识点,C#是一种简洁的,类型安全的一种完全面向对象的开发语言,是Microsoft专门基于.NET Framework平台开发的而量身定做的高级程序设计语言,需要的朋友可以参考下...2020-06-25
  • C#变量命名规则小结

    本文主要介绍了C#变量命名规则小结,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-09-09
  • c#中(&&,||)与(&,|)的区别详解

    这篇文章主要介绍了c#中(&&,||)与(&,|)的区别详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-06-25
  • C# 中如何取绝对值函数

    本文主要介绍了C# 中取绝对值的函数。具有很好的参考价值。下面跟着小编一起来看下吧...2020-06-25
  • C#绘制曲线图的方法

    这篇文章主要介绍了C#绘制曲线图的方法,以完整实例形式较为详细的分析了C#进行曲线绘制的具体步骤与相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下...2020-06-25
  • c#自带缓存使用方法 c#移除清理缓存

    这篇文章主要介绍了c#自带缓存使用方法,包括获取数据缓存、设置数据缓存、移除指定数据缓存等方法,需要的朋友可以参考下...2020-06-25
  • C#学习笔记- 随机函数Random()的用法详解

    下面小编就为大家带来一篇C#学习笔记- 随机函数Random()的用法详解。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2020-06-25
  • C#中list用法实例

    这篇文章主要介绍了C#中list用法,结合实例形式分析了C#中list排序、运算、转换等常见操作技巧,具有一定参考借鉴价值,需要的朋友可以参考下...2020-06-25