当前位置: 首页 > news >正文

浅谈C# record关键字

环境:.net8控制台

init关键字

通常我们会有一个常见的需求就是需要实现一个实例化后不可变的类型.

我通常会如下实现,将类的属性的set设为私有,这样只能使用构造函数来实例一个不可变对象.

但是如果内部再声明一个public的方法还是有可能会将我这个对象改变.

    internal class Program{static void Main(string[] args){Person person = new Person(1, "tom");person.SetValue(2, "Trump");Console.WriteLine(person.Name);Console.WriteLine(person.Id);}}    public class Person{public int Id { get; private set; }public string Name { get; private set; }public Person(int Id, string Name){this.Id = Id;this.Name = Name;}public void SetValue(int Id, string Name){this.Id = Id;this.Name = Name;}}

但我们可以使用init关键字取代原来的private set,这样即便想在类内部设置一个方法修改属性也是不成立的了,因为此时编译器要求只能在声明时赋值,构造函数中赋值和对象初始化器赋值,而禁止其他形式的赋值. 

 

什么是对象初始化器赋值?

使用这个{Id=1,Name="Tom"},这样的形式就是对象初始化器赋值.这是一种语法糖.如下代码

声明时赋值,构造函数中赋值和对象初始化器赋值,这三种赋值也是有顺序的

首先是声明时赋值,然后是构造器赋值,最后是对象初始化器赋值.

虽然有了init关键字帮助我们实现了对象的属性的不可变,但还不够,一般还伴随着要重新Tostring,Equals等方法.

通常我们还希望两个属性一致的对象是相等的,这我们就不得不重新Equals.几个类倒也没什么,但是如果这样的类多了,我们就做了很多重复的工作,还好.net为我们提供了record关键字.

 record关键字

现在我们只需要一行就能完美实现上述需求.

但是我们有必要知道的是init关键字和record在实现上没有关系.只是在设计理念上有相似的地方,同时要知道的是init比record更"宽松".

宽松如何理解?

前面我们提到init可以在对象初始化器中赋值,然后属性才会被冻结,这其实就是在构造函数结束后还有机会再次被赋值,而record声明的类,严格控制到构造函数之前赋值,离开构造函数就没有机会赋值了.

 record的本质就是一个语法糖,编译器为我们做了很多事,这是我反编译Person类的结果.本质还是一个类.

// Decompiled with JetBrains decompiler
// Type: RecordStudy.Person
// Assembly: RecordStudy, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
// MVID: 60251223-809A-4A03-A8DA-EDD2743C7E5A
// Assembly location: E:\DonetProjects\RecordStudy\bin\Debug\net8.0\RecordStudy.dll
// Local variable names from E:\DonetProjects\RecordStudy\bin\Debug\net8.0\RecordStudy.pdb
// Compiler-generated code is shownusing System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Text;namespace RecordStudy
{[NullableContext(1)][Nullable(0)]public class Person : /*[Nullable(0)]*/IEquatable<Person>{[CompilerGenerated][DebuggerBrowsable(DebuggerBrowsableState.Never)]private readonly int \u003CId\u003Ek__BackingField;[CompilerGenerated][DebuggerBrowsable(DebuggerBrowsableState.Never)]private readonly string \u003CName\u003Ek__BackingField;public Person(int Id, string Name){this.\u003CId\u003Ek__BackingField = Id;this.\u003CName\u003Ek__BackingField = Name;base.\u002Ector();}[CompilerGenerated]protected virtual Type EqualityContract{[CompilerGenerated] get{return typeof (Person);}}public int Id{[CompilerGenerated] get{return this.\u003CId\u003Ek__BackingField;}[CompilerGenerated] init{this.\u003CId\u003Ek__BackingField = value;}}public string Name{[CompilerGenerated] get{return this.\u003CName\u003Ek__BackingField;}[CompilerGenerated] init{this.\u003CName\u003Ek__BackingField = value;}}[CompilerGenerated]public override string ToString(){StringBuilder builder = new StringBuilder();builder.Append("Person");builder.Append(" { ");if (this.PrintMembers(builder))builder.Append(' ');builder.Append('}');return builder.ToString();}[CompilerGenerated]protected virtual bool PrintMembers(StringBuilder builder){RuntimeHelpers.EnsureSufficientExecutionStack();builder.Append("Id = ");builder.Append(this.Id.ToString());builder.Append(", Name = ");builder.Append((object) this.Name);return true;}[NullableContext(2)][CompilerGenerated][SpecialName]public static bool op_Inequality(Person left, Person right){return !Person.op_Equality(left, right);}[NullableContext(2)][CompilerGenerated][SpecialName]public static bool op_Equality(Person left, Person right){if ((object) left == (object) right)return true;return (object) left != null && left.Equals(right);}[CompilerGenerated]public override int GetHashCode(){return (EqualityComparer<Type>.Default.GetHashCode(this.EqualityContract) * -1521134295 + EqualityComparer<int>.Default.GetHashCode(this.\u003CId\u003Ek__BackingField)) * -1521134295 + EqualityComparer<string>.Default.GetHashCode(this.\u003CName\u003Ek__BackingField);}[NullableContext(2)][CompilerGenerated]public override bool Equals(object obj){return this.Equals(obj as Person);}[NullableContext(2)][CompilerGenerated]public virtual bool Equals(Person other){if ((object) this == (object) other)return true;return (object) other != null && Type.op_Equality(this.EqualityContract, other.EqualityContract) && EqualityComparer<int>.Default.Equals(this.\u003CId\u003Ek__BackingField, other.\u003CId\u003Ek__BackingField) && EqualityComparer<string>.Default.Equals(this.\u003CName\u003Ek__BackingField, other.\u003CName\u003Ek__BackingField);}[CompilerGenerated]public virtual Person \u003CClone\u003E\u0024(){return new Person(this);}[CompilerGenerated]protected Person(Person original){base.\u002Ector();this.\u003CId\u003Ek__BackingField = original.\u003CId\u003Ek__BackingField;this.\u003CName\u003Ek__BackingField = original.\u003CName\u003Ek__BackingField;}[CompilerGenerated]public void Deconstruct(out int Id, out string Name){Id = this.Id;Name = this.Name;}}
}

现在我们的Person类的两个属性都是只读的了,但是万一我们还有需求要添加一个可读可写的属性,也有办法.

只需要如下再添加一个属性,同时观察Tostring方法,虽然NickName属性特殊一点,但是并没有被Tostring方法忘记,Equals方法也是同理.

当然不仅是再添加属性,还能添加构造函数

注意:

record声明的类也是普通的类,变量的赋值也是引用的传递.

如何深拷贝一个对象呢?可以使用with

with一个给到另一个对象即完成了深拷贝. 同时如果你想改一些值也是可以的.

相关文章:

  • DeepSeek谈《凤凰项目 一个IT运维的传奇故事》
  • 蛋白质数据库UniProt介绍
  • git中reset和checkout的用法
  • Webug4.0通关笔记06- 第8关CSV注入
  • 文件读取操作
  • 【论文速读】《Scaling Scaling Laws with Board Games》
  • 数据结构学习篇——哈希
  • 冰冰一号教程网--介绍采用vuepress搭建个人博客
  • Git 忽略文件配置 .gitignore
  • 客户服务升级:智能语音外呼系统在多领域的场景应用解析
  • navicat中导出数据表结构并在word更改为三线表(适用于navicat导不出doc)
  • rails 创建数据库表
  • java实现序列化与反序列化
  • halcon打开图形窗口
  • SpringBoot+Redis全局唯一ID生成器
  • Vue3中到达可视区域后执行
  • Tauri v1 与 v2 配置对比
  • C++好用的打印日志类
  • Cangjie Magic在医疗领域的应用:智能体技术如何重塑医疗数字化
  • 科研 | 光子技术为人工智能注入新动力
  • 市场监管总局出手整治涉企乱收费,聚焦政府部门及下属单位等领域
  • 神十九飞船已撤离空间站,计划于今日中午返回东风着陆场
  • 陈文清:推进扫黑除恶常态化走深走实,有力回应人民群众对安居乐业的新期待
  • 在岸、离岸人民币对美元汇率双双升破7.26关口
  • 从腰缠万贯到债台高筑、官司缠身:尼泊尔保皇新星即将陨落?
  • 吕国范任河南省人民政府副省长