c#编码技巧(十八):新语法糖record深入分析

2023-12-13 21:04:32

c#编码技巧(十四):新语法糖record深入分析

从 C# 9 开始新增了一个关键字record,用于封装数据。
record实质是微软提供的一个语法糖,因很多开源项目都用到了这个关键字,说明这个语法糖比较实用。
那么这个record类型和普通class类型有什么区别呢?我们可以通过工具探究一下源码
首先新建一个普通的类PersonCls,添加两个属性

    /// <summary>
    /// 普通类
    /// </summary>
    public class PersonCls
    {
        public string Name { get; set; }
        public string Address { get; set; }
    }

再建一个record类型PersonRecord,record类型的声明简洁:一行就搞定,其中属性名字放在括号里,编译器自动为我们生成两个属性

  
    /// <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代码,果然非常普通,除了两属性什么都没有

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: 0x0000029A
		public 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: 0x000002AB
		public string Address { get; set; }
	}
}

同样利用反编译工具查看PersonRecord,却生成了很多代码
其中

  • 包含一个有参构造函数
  • 生成了两个属性
  • 生成了ToString和运算符比较、Equals比较的方法
  • 还有其他一些方法,方法都很简单暂不做分析
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: 0x000002BD
		public 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: 0x000002E8
		public 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: 0x000002F9
		public 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()可以输出值
  • 属性值相同的两个record类型,使用==或Equals比较,判断为相等
        static async Task Main(string[] args)
        {
            //record类型ToString()可以输出名称+值,而类只会输出命名空间+类名
            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;//false
            bool isClassEquals = cls1.Equals(cls2);//false
            bool isClassReferenceEquls = ReferenceEquals(cls1, cls2);//false

            //属性值相同的两个record类型,使用==或Equals比较,判断为相等;使用ReferenceEquals比较,判断为不相等,因为引用是确实是不同
            var rcd1 = new PersonRecord("Ben", "HK");
            var rcd2 = new PersonRecord("Ben", "HK");
            bool isRecordOperatorEquals = rcd1 == rcd2;// true:因为值相同
            bool isRecordEquals = rcd1.Equals(rcd2);//true:因为值相同
            bool isRecordReferenceEquls = ReferenceEquals(rcd1, rcd2);//false:引用不同

            //rcd1.Address = "shenzhen";//定义在括号内的record的属性值,在new之后不能修改;
            //想要能修改的属性,就像Level那样声明
            /*
                public record PersonRecordCanChange(string Name, string Address)
                {
                    public string Level { get; set; }
                }
             */
        }

record还有其他用法,比如使用with { }可以复制这个record

            var rcdCopy = rcd1 with { };//复制
            Console.WriteLine($"name={rcdCopy.Name}, address={rcdCopy.Address}");//name=Ben, address=HK

只想部分复制,可以在{}内更改部分属性的值

            var rcdCopy = rcd1 with { };//复制
            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);

如果确实需要自定义属性,可以这样写

	
    public record Points
    {
    	//声明属性X,Y,并在构造函数中注入赋值
        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的设计初衷

综上所述:

  1. record实质是微软提供的一个语法糖,本质就是一个类,这个类里生成了若干个方法,这些方法就使得record与类区别开来
  2. record的写法简单快捷,可用在数据传输对象中,如dto等,能够大大提升效率,简化代码

文章来源:https://blog.csdn.net/u011340439/article/details/130991835
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。