環境:.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一個給到另一個對象即完成了深拷貝. 同時如果你想改一些值也是可以的.