Intro
C# 9 中引入了 record,record 是一个特殊类,用它来实现 model 在有些情况下会非常的好用
Sample
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28record RecordPerson { public string Name { get; init; } public int Age { get; init; } } record RecordPerson2(string Name, int Age); public static void MainTest() { var p1 = new RecordPerson() { Name = "Tom", Age = 12, }; Console.WriteLine(p1); var p2 = p1 with { Age = 10 }; Console.WriteLine(p2); var p3 = new RecordPerson() { Name = "Tom", Age = 12 }; Console.WriteLine(p3); Console.WriteLine($"p1 Equals p3 =:{p1 == p3}"); RecordPerson2 p4 = new("Tom", 12); Console.WriteLine(p4); }
这里的示例,用 record
声明了两个 model,第二个 model 声明的时候使用了简化的写法,
record RecordPerson2(string Name, int Age);
这样的声明意味着,构造方法有两个参数,分别是 string Name
和 int Age
,并对应着两个属性,属性的声明方式和 RecordPerson
一样 public string Name { get; init; }
都是一个 get
一个 init
对于 record
支持一个 with
表达式,来修改某几个属性的值,这对于有很多属性都相同的场景来说是及其方便的,来看一下上面示例的输出结果
What inside
那么 record
内部发生了什么呢,我们来反编译看一下,我们看一下使用 DnSpy
反编译的结果
RecordPerson
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140private class RecordPerson : IEquatable<RecordSample.RecordPerson> { // Token: 0x17000007 RID: 7 // (get) Token: 0x06000027 RID: 39 RVA: 0x000025F4 File Offset: 0x000007F4 [Nullable(1)] protected virtual Type EqualityContract { [NullableContext(1)] [CompilerGenerated] get { return typeof(RecordSample.RecordPerson); } } // Token: 0x17000008 RID: 8 // (get) Token: 0x06000028 RID: 40 RVA: 0x00002600 File Offset: 0x00000800 // (set) Token: 0x06000029 RID: 41 RVA: 0x00002608 File Offset: 0x00000808 public string Name { [CompilerGenerated] get { return this.<Name>k__BackingField; } [CompilerGenerated] set { this.<Name>k__BackingField = value; } } // Token: 0x17000009 RID: 9 // (get) Token: 0x0600002A RID: 42 RVA: 0x00002611 File Offset: 0x00000811 // (set) Token: 0x0600002B RID: 43 RVA: 0x00002619 File Offset: 0x00000819 public int Age { [CompilerGenerated] get { return this.<Age>k__BackingField; } [CompilerGenerated] set { this.<Age>k__BackingField = value; } } // Token: 0x0600002C RID: 44 RVA: 0x00002624 File Offset: 0x00000824 public override string ToString() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("RecordPerson"); stringBuilder.Append(" { "); if (this.PrintMembers(stringBuilder)) { stringBuilder.Append(" "); } stringBuilder.Append("}"); return stringBuilder.ToString(); } // Token: 0x0600002D RID: 45 RVA: 0x00002678 File Offset: 0x00000878 [NullableContext(1)] protected virtual bool PrintMembers(StringBuilder builder) { builder.Append("Name"); builder.Append(" = "); builder.Append(this.Name); builder.Append(", "); builder.Append("Age"); builder.Append(" = "); builder.Append(this.Age.ToString()); return true; } // Token: 0x0600002E RID: 46 RVA: 0x000026EA File Offset: 0x000008EA [NullableContext(2)] public static bool operator !=(RecordSample.RecordPerson r1, RecordSample.RecordPerson r2) { return !(r1 == r2); } // Token: 0x0600002F RID: 47 RVA: 0x000026F6 File Offset: 0x000008F6 [NullableContext(2)] public static bool operator ==(RecordSample.RecordPerson r1, RecordSample.RecordPerson r2) { return r1 == r2 || (r1 != null && r1.Equals(r2)); } // Token: 0x06000030 RID: 48 RVA: 0x0000270C File Offset: 0x0000090C public override int GetHashCode() { return (EqualityComparer<Type>.Default.GetHashCode(this.EqualityContract) * -1521134295 + EqualityComparer<string>.Default.GetHashCode(this.<Name>k__BackingField)) * -1521134295 + EqualityComparer<int>.Default.GetHashCode(this.<Age>k__BackingField); } // Token: 0x06000031 RID: 49 RVA: 0x0000274C File Offset: 0x0000094C [NullableContext(2)] public override bool Equals(object obj) { return this.Equals(obj as RecordSample.RecordPerson); } // Token: 0x06000032 RID: 50 RVA: 0x0000275C File Offset: 0x0000095C [NullableContext(2)] public virtual bool Equals(RecordSample.RecordPerson other) { return other != null && this.EqualityContract == other.EqualityContract && EqualityComparer<string>.Default.Equals(this.<Name>k__BackingField, other.<Name>k__BackingField) && EqualityComparer<int>.Default.Equals(this.<Age>k__BackingField, other.<Age>k__BackingField); } // Token: 0x06000033 RID: 51 RVA: 0x000027B0 File Offset: 0x000009B0 [NullableContext(1)] public virtual RecordSample.RecordPerson <Clone>$() { return new RecordSample.RecordPerson(this); } // Token: 0x06000034 RID: 52 RVA: 0x000027B8 File Offset: 0x000009B8 protected RecordPerson([Nullable(1)] RecordSample.RecordPerson original) { this.Name = original.<Name>k__BackingField; this.Age = original.<Age>k__BackingField; } // Token: 0x06000035 RID: 53 RVA: 0x000027D9 File Offset: 0x000009D9 public RecordPerson() { } // Token: 0x0400000C RID: 12 [CompilerGenerated] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly string <Name>k__BackingField; // Token: 0x0400000D RID: 13 [CompilerGenerated] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly int <Age>k__BackingField; }
从上面的反编译结果可以看的出来,record
其实就是一个 class
,只是编译器会帮我们做一些事情,编译器帮我们做了哪些事呢?
- 实现了基于属性值的相等性比较,不再使用默认的引用,并且重写了 ==/!= operator 和
GetHashCode
- 为了方便调试,重写了
ToString
方法,也提供了PrintMembers
方法来实现比较方便只显示某些比较重要的参数 - 实现了
EqualityContract
方法来指定类型比较的类型,默认是当前类型 - 实现了
<Clone>$
方法和一个特殊的构造方法,用来克隆一个对象,相当于record
帮我们实现了一个浅复制 的 原型模式,还是强类型的,这个方法在代码里不能直接调用,当我们使用with
表达式的时候,编译器会调用这个方法,并对某些属性进行赋值
再来看一下 RecordPerson2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150private class RecordPerson2 : IEquatable<RecordSample.RecordPerson2> { // Token: 0x06000036 RID: 54 RVA: 0x000027E2 File Offset: 0x000009E2 public RecordPerson2(string Name, int Age) { this.Name = Name; this.Age = Age; base..ctor(); } // Token: 0x1700000A RID: 10 // (get) Token: 0x06000037 RID: 55 RVA: 0x000027F9 File Offset: 0x000009F9 [Nullable(1)] protected virtual Type EqualityContract { [NullableContext(1)] [CompilerGenerated] get { return typeof(RecordSample.RecordPerson2); } } // Token: 0x1700000B RID: 11 // (get) Token: 0x06000038 RID: 56 RVA: 0x00002805 File Offset: 0x00000A05 // (set) Token: 0x06000039 RID: 57 RVA: 0x0000280D File Offset: 0x00000A0D public string Name { [CompilerGenerated] get { return this.<Name>k__BackingField; } [CompilerGenerated] set { this.<Name>k__BackingField = value; } } // Token: 0x1700000C RID: 12 // (get) Token: 0x0600003A RID: 58 RVA: 0x00002816 File Offset: 0x00000A16 // (set) Token: 0x0600003B RID: 59 RVA: 0x0000281E File Offset: 0x00000A1E public int Age { [CompilerGenerated] get { return this.<Age>k__BackingField; } [CompilerGenerated] set { this.<Age>k__BackingField = value; } } // Token: 0x0600003C RID: 60 RVA: 0x00002828 File Offset: 0x00000A28 public override string ToString() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("RecordPerson2"); stringBuilder.Append(" { "); if (this.PrintMembers(stringBuilder)) { stringBuilder.Append(" "); } stringBuilder.Append("}"); return stringBuilder.ToString(); } // Token: 0x0600003D RID: 61 RVA: 0x0000287C File Offset: 0x00000A7C [NullableContext(1)] protected virtual bool PrintMembers(StringBuilder builder) { builder.Append("Name"); builder.Append(" = "); builder.Append(this.Name); builder.Append(", "); builder.Append("Age"); builder.Append(" = "); builder.Append(this.Age.ToString()); return true; } // Token: 0x0600003E RID: 62 RVA: 0x000028EE File Offset: 0x00000AEE [NullableContext(2)] public static bool operator !=(RecordSample.RecordPerson2 r1, RecordSample.RecordPerson2 r2) { return !(r1 == r2); } // Token: 0x0600003F RID: 63 RVA: 0x000028FA File Offset: 0x00000AFA [NullableContext(2)] public static bool operator ==(RecordSample.RecordPerson2 r1, RecordSample.RecordPerson2 r2) { return r1 == r2 || (r1 != null && r1.Equals(r2)); } // Token: 0x06000040 RID: 64 RVA: 0x00002910 File Offset: 0x00000B10 public override int GetHashCode() { return (EqualityComparer<Type>.Default.GetHashCode(this.EqualityContract) * -1521134295 + EqualityComparer<string>.Default.GetHashCode(this.<Name>k__BackingField)) * -1521134295 + EqualityComparer<int>.Default.GetHashCode(this.<Age>k__BackingField); } // Token: 0x06000041 RID: 65 RVA: 0x00002950 File Offset: 0x00000B50 [NullableContext(2)] public override bool Equals(object obj) { return this.Equals(obj as RecordSample.RecordPerson2); } // Token: 0x06000042 RID: 66 RVA: 0x00002960 File Offset: 0x00000B60 [NullableContext(2)] public virtual bool Equals(RecordSample.RecordPerson2 other) { return other != null && this.EqualityContract == other.EqualityContract && EqualityComparer<string>.Default.Equals(this.<Name>k__BackingField, other.<Name>k__BackingField) && EqualityComparer<int>.Default.Equals(this.<Age>k__BackingField, other.<Age>k__BackingField); } // Token: 0x06000043 RID: 67 RVA: 0x000029B4 File Offset: 0x00000BB4 [NullableContext(1)] public virtual RecordSample.RecordPerson2 <Clone>$() { return new RecordSample.RecordPerson2(this); } // Token: 0x06000044 RID: 68 RVA: 0x000029BC File Offset: 0x00000BBC protected RecordPerson2([Nullable(1)] RecordSample.RecordPerson2 original) { this.Name = original.<Name>k__BackingField; this.Age = original.<Age>k__BackingField; } // Token: 0x06000045 RID: 69 RVA: 0x000029DD File Offset: 0x00000BDD public void Deconstruct(out string Name, out int Age) { Name = this.Name; Age = this.Age; } // Token: 0x0400000E RID: 14 [CompilerGenerated] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly string <Name>k__BackingField; // Token: 0x0400000F RID: 15 [CompilerGenerated] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly int <Age>k__BackingField; }
RecordPerson2
相比 RecordPerson
的区别在于构造器不同:
看上面反编译的结果,可以看出:
RecordPeron2
和RecordPerson
都声明了两个属性,都是public string Name { get; init; }/public int Age { get; init; }
RecordPerson
的构造方法是无参构造方法,而RecordPerson2
的构造方法是RecordPerson2(string Name, int Age)
- 多出来一个
Deconstruct
方法,使得我们可以比较方便的解析一个对象的值,可以参考下面这个示例
1
2
3
4foreach (var (name, age) in new[] { p4 }) { Console.WriteLine($"{name}={age}"); }
再来看一下测试方法的反编译结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19RecordSample.RecordPerson p = new RecordSample.RecordPerson { Name = "Tom", Age = 12 }; Console.WriteLine(p); RecordSample.RecordPerson recordPerson = p.<Clone>$(); recordPerson.Age = 10; RecordSample.RecordPerson p2 = recordPerson; Console.WriteLine(p2); RecordSample.RecordPerson p3 = new RecordSample.RecordPerson { Name = "Tom", Age = 12 }; Console.WriteLine(p3); Console.WriteLine(string.Format("p1 Equals p3 =:{0}", p == p3)); RecordSample.RecordPerson2 p4 = new RecordSample.RecordPerson2("Tom", 12); Console.WriteLine(p4);
这里主要可以看到 with
表达式的实现,其实就是调用 <Clone>$
方法复制了一个对象,并修改指定的属性值
More
record
实现了基于值的相等性比较,并且实现了 原型模式,可以比较方便的创建一个新的值完全相等的对象,这对于有一些业务场景来说是非常适合使用 record
来代替原来的实现的
Reference
https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-9
https://github.com/WeihanLi/SamplesInPractice/tree/master/CSharp9Sample
https://github.com/WeihanLi/SamplesInPractice/blob/master/CSharp9Sample/RecordSample.cs
以上就是C# 9 新特性——record的相关总结的详细内容,更多关于c# 9 新特性的资料请关注靠谱客其它相关文章!
最后
以上就是开放汉堡最近收集整理的关于C# 9 新特性——record的相关总结的全部内容,更多相关C#内容请搜索靠谱客的其他文章。
发表评论 取消回复