微信是哪個(gè)公司開(kāi)發(fā)的軟件seo網(wǎng)站排名優(yōu)化公司哪家好
c#編碼技巧(十四):新語(yǔ)法糖record深入分析
從 C# 9 開(kāi)始新增了一個(gè)關(guān)鍵字record,用于封裝數(shù)據(jù)。
record實(shí)質(zhì)是微軟提供的一個(gè)語(yǔ)法糖,因很多開(kāi)源項(xiàng)目都用到了這個(gè)關(guān)鍵字,說(shuō)明這個(gè)語(yǔ)法糖比較實(shí)用。
那么這個(gè)record類型和普通class類型有什么區(qū)別呢?我們可以通過(guò)工具探究一下源碼
首先新建一個(gè)普通的類PersonCls,添加兩個(gè)屬性
/// <summary>/// 普通類/// </summary>public class PersonCls{public string Name { get; set; }public string Address { get; set; }}
再建一個(gè)record類型PersonRecord,record類型的聲明簡(jiǎn)潔:一行就搞定,其中屬性名字放在括號(hào)里,編譯器自動(dòng)為我們生成兩個(gè)屬性
/// <summary>/// record類型/// </summary>/// <param name="Name"></param>/// <param name="Age"></param>public record PersonRecord(string Name, int Age);//也即public record class PersonRecord(string Name, int Age);//其中class可省略var person = new PersonRecord("Tom", 18);
利用反編譯工具查看普通類PersonCls代碼,果然非常普通,除了兩屬性什么都沒(méi)有
using System;
using System.Runtime.CompilerServices;namespace DotNet6
{// Token: 0x02000005 RID: 5[NullableContext(1)][Nullable(0)]public class PersonCls{// Token: 0x17000001 RID: 1// (get) Token: 0x06000005 RID: 5 RVA: 0x00002092 File Offset: 0x00000292// (set) Token: 0x06000006 RID: 6 RVA: 0x0000209A File Offset: 0x0000029Apublic string Name { get; set; }// Token: 0x17000002 RID: 2// (get) Token: 0x06000007 RID: 7 RVA: 0x000020A3 File Offset: 0x000002A3// (set) Token: 0x06000008 RID: 8 RVA: 0x000020AB File Offset: 0x000002ABpublic string Address { get; set; }}
}
同樣利用反編譯工具查看PersonRecord,卻生成了很多代碼
其中
- 包含一個(gè)有參構(gòu)造函數(shù)
- 生成了兩個(gè)屬性
- 生成了ToString和運(yùn)算符比較、Equals比較的方法
- 還有其他一些方法,方法都很簡(jiǎn)單暫不做分析
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Text;namespace DotNet6
{// Token: 0x02000006 RID: 6[NullableContext(1)][Nullable(0)]public class PersonRecord : IEquatable<PersonRecord>{// Token: 0x0600000A RID: 10 RVA: 0x000020BD File Offset: 0x000002BDpublic PersonRecord(string Name, string Address){this.Name = Name;this.Address = Address;base..ctor();}// Token: 0x17000003 RID: 3// (get) Token: 0x0600000B RID: 11 RVA: 0x000020D4 File Offset: 0x000002D4[CompilerGenerated]protected virtual Type EqualityContract{[CompilerGenerated]get{return typeof(PersonRecord);}}// Token: 0x17000004 RID: 4// (get) Token: 0x0600000C RID: 12 RVA: 0x000020E0 File Offset: 0x000002E0// (set) Token: 0x0600000D RID: 13 RVA: 0x000020E8 File Offset: 0x000002E8public string Name { get; set; }// Token: 0x17000005 RID: 5// (get) Token: 0x0600000E RID: 14 RVA: 0x000020F1 File Offset: 0x000002F1// (set) Token: 0x0600000F RID: 15 RVA: 0x000020F9 File Offset: 0x000002F9public string Address { get; set; }// Token: 0x06000010 RID: 16 RVA: 0x00002104 File Offset: 0x00000304[CompilerGenerated]public override string ToString(){StringBuilder stringBuilder = new StringBuilder();stringBuilder.Append("PersonRecord");stringBuilder.Append(" { ");if (this.PrintMembers(stringBuilder)){stringBuilder.Append(' ');}stringBuilder.Append('}');return stringBuilder.ToString();}// Token: 0x06000011 RID: 17 RVA: 0x00002150 File Offset: 0x00000350[CompilerGenerated]protected virtual bool PrintMembers(StringBuilder builder){RuntimeHelpers.EnsureSufficientExecutionStack();builder.Append("Name = ");builder.Append(this.Name);builder.Append(", Address = ");builder.Append(this.Address);return true;}// Token: 0x06000012 RID: 18 RVA: 0x0000218A File Offset: 0x0000038A[NullableContext(2)][CompilerGenerated]public static bool operator !=(PersonRecord left, PersonRecord right){return !(left == right);}// Token: 0x06000013 RID: 19 RVA: 0x00002196 File Offset: 0x00000396[NullableContext(2)][CompilerGenerated]public static bool operator ==(PersonRecord left, PersonRecord right){return left == right || (left != null && left.Equals(right));}// Token: 0x06000014 RID: 20 RVA: 0x000021AC File Offset: 0x000003AC[CompilerGenerated]public override int GetHashCode(){return (EqualityComparer<Type>.Default.GetHashCode(this.EqualityContract) * -1521134295 + EqualityComparer<string>.Default.GetHashCode(this.<Name>k__BackingField)) * -1521134295 + EqualityComparer<string>.Default.GetHashCode(this.<Address>k__BackingField);}// Token: 0x06000015 RID: 21 RVA: 0x000021EC File Offset: 0x000003EC[NullableContext(2)][CompilerGenerated]public override bool Equals(object obj){return this.Equals(obj as PersonRecord);}// Token: 0x06000016 RID: 22 RVA: 0x000021FC File Offset: 0x000003FC[NullableContext(2)][CompilerGenerated]public virtual bool Equals(PersonRecord other){return this == other || (other != null && this.EqualityContract == other.EqualityContract && EqualityComparer<string>.Default.Equals(this.<Name>k__BackingField, other.<Name>k__BackingField) && EqualityComparer<string>.Default.Equals(this.<Address>k__BackingField, other.<Address>k__BackingField));}// Token: 0x06000018 RID: 24 RVA: 0x0000225F File Offset: 0x0000045F[CompilerGenerated]protected PersonRecord(PersonRecord original){this.Name = original.<Name>k__BackingField;this.Address = original.<Address>k__BackingField;}// Token: 0x06000019 RID: 25 RVA: 0x00002280 File Offset: 0x00000480[CompilerGenerated]public void Deconstruct(out string Name, out string Address){Name = this.Name;Address = this.Address;}}
}
使用這些方法,可以看到與普通class類不同的是:
- record的ToString()可以輸出值
- 屬性值相同的兩個(gè)record類型,使用==或Equals比較,判斷為相等
static async Task Main(string[] args){//record類型ToString()可以輸出名稱+值,而類只會(huì)輸出命名空間+類名var clsStr = (new PersonCls() { Name = "LiLei", Address="GD" }.ToString());var str = (new PersonRecord("LiLei", "GD")).ToString();//輸出:PersonRecord { Name = LiLei, Address = GD }//屬性值相同的普通類,使用==或Equals比較,判斷為不相等var cls1 = new PersonCls() { Name = "Tom", Address = "CN" };var cls2 = new PersonCls() { Name = "Tom", Address = "CN" };bool isClassOperatorEquals = cls1 == cls2;//falsebool isClassEquals = cls1.Equals(cls2);//falsebool isClassReferenceEquls = ReferenceEquals(cls1, cls2);//false//屬性值相同的兩個(gè)record類型,使用==或Equals比較,判斷為相等;使用ReferenceEquals比較,判斷為不相等,因?yàn)橐檬谴_實(shí)是不同var rcd1 = new PersonRecord("Ben", "HK");var rcd2 = new PersonRecord("Ben", "HK");bool isRecordOperatorEquals = rcd1 == rcd2;// true:因?yàn)橹迪嗤?/span>bool isRecordEquals = rcd1.Equals(rcd2);//true:因?yàn)橹迪嗤?/span>bool isRecordReferenceEquls = ReferenceEquals(rcd1, rcd2);//false:引用不同//rcd1.Address = "shenzhen";//定義在括號(hào)內(nèi)的record的屬性值,在new之后不能修改;//想要能修改的屬性,就像Level那樣聲明/*public record PersonRecordCanChange(string Name, string Address){public string Level { get; set; }}*/}
record還有其他用法,比如使用with { }可以復(fù)制這個(gè)record
var rcdCopy = rcd1 with { };//復(fù)制Console.WriteLine($"name={rcdCopy.Name}, address={rcdCopy.Address}");//name=Ben, address=HK
只想部分復(fù)制,可以在{}內(nèi)更改部分屬性的值
var rcdCopy = rcd1 with { };//復(fù)制Console.WriteLine($"name={rcdCopy.Name}, address={rcdCopy.Address}");//name=Ben, address=Beijing
可以繼承
public record Animal(string Head, string Body);public record Human(string Head, string Body, string Hand): Animal(Head, Body);
如果確實(shí)需要自定義屬性,可以這樣寫
public record Points{//聲明屬性X,Y,并在構(gòu)造函數(shù)中注入賦值public Points(double time, double distance) => (X, Y) = (time, distance);public double X { get; set; }public double Y { get; set; }}var point = new Points(time: 10, distance: 990);var x = point.X;var y = point.Y;
但不建議這樣做,這樣違背了record的設(shè)計(jì)初衷
綜上所述:
- record實(shí)質(zhì)是微軟提供的一個(gè)語(yǔ)法糖,本質(zhì)就是一個(gè)類,這個(gè)類里生成了若干個(gè)方法,這些方法就使得record與類區(qū)別開(kāi)來(lái)
- record的寫法簡(jiǎn)單快捷,可用在數(shù)據(jù)傳輸對(duì)象中,如dto等,能夠大大提升效率,簡(jiǎn)化代碼