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

浅谈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一个给到另一个对象即完成了深拷贝. 同时如果你想改一些值也是可以的.

http://www.xdnf.cn/news/3149.html

相关文章:

  • CSS:选择器-基本选择器
  • linux联接服务器SSH-局域网内网穿透分享
  • VR 汽车线束培训:探索高效学习新路径​
  • 线性微分方程与非线性微分方程
  • 工业控制「混合架构」PK大战 —— 神经网络 + MPC vs 模糊 PID+MPC 的场景选型与实战指南
  • 数据转储(go)
  • 网络原理 - 12(HTTP/HTTPS - 3 - 响应)
  • 人工智能数学基础(六):数理统计
  • [Android]任务列表中有两个相机图标
  • 2025上海车展 | 移远通信推出自研NG-eCall QuecOpen方案,助力汽车安全新标准加速落地
  • 青少年抑郁症患者亚群结构和功能连接耦合的重构
  • 2025年“深圳杯”数学建模挑战赛B题-LED显示屏颜色转换设计与校正
  • Java从入门到精通 - Java入门
  • 极光PDF编辑器:高效编辑,轻松管理PDF文档
  • 相机的基础架构
  • TwinCAT数据类型,%MX,%MD这些特殊符号
  • 解决 RN Switch 组件在安卓端样式很丑的问题
  • MySQL 基本查询(一)
  • SNR8016语音模块详解(STM32)
  • Python中的内置函数
  • Django 自定义celery-beat调度器,查询自定义表的Cron表达式进行任务调度
  • K8S - GitOps 入门实战 - 自动发布与秒级回滚
  • 运维仙途 第1章 灵机突现探监控
  • 【文献速递】邻位连接技术(PLA)在细胞器相互作用中的应用
  • 汽车免拆诊断案例 | 2015款奔驰C200L车发动机起动延迟
  • AI预测3D新模型百十个定位预测+胆码预测+去和尾2025年4月30日第68弹
  • springboot集成Lucene详细使用
  • NVIDIA DRIVE AGX平台:引领智能驾驶安全新时代
  • 使用 Vue 开发 VS Code 插件前端页面(上)
  • 「Unity3D」TextMeshPro使用TMP_InputField实现,输入框高度自动扩展与收缩