C#中使用 record 的好处 因为好用所以推荐~
<p>一晃距<code>C# 9</code>发布已经4年了,对于<code>record</code>关键字想必大家都不陌生了,不过呢发现还是有很多同学不屑于使用这个<code>语法糖</code>,确实,本质上 record 就是 class 的封装,能用 record 书写的类,那100%都是可以自己手撸出来的,但是呢有没有考虑 别人可能一分钟写好的代码你可能会需要数分钟才能完成.因此为了能有更多时间 <s>摸鱼</s> ,强烈推荐不屑一顾的同学也能用起来!</p><p>下面我简略聊一聊 record 的好处和最佳场景:</p>
<ol>
<li>简化语法</li>
</ol>
<p>我们只需要一行代码就可以定义完成,这个是最直观节省编码的方式,我们不需要编写一堆枯燥的<em>get;set;</em> 也不需要编写构造函数等样板代码:</p>
<pre><code class="language-csharp">public record Person(string FirstName, string LastName);
</code></pre>
<p>那么有同学会有疑问,如果Person有很多的属性咋整,不就意味着主构造函数会很冗长,其实呢,这个和封装传参的方式是一样的,我们可以把同质的属性封装成其他的record或者class,比如:</p>
<pre><code class="language-csharp">public record ExtraInfomation(string Address,string Email,int Age);
public record Person(string FirstName, string LastName, ExtraInfomation ExtraInfo);
</code></pre>
<ol start="2">
<li>自动生成一些对我们有用的成员函数.</li>
</ol>
<ul>
<li>构造函数:根据定义的属性自动生成构造函数。</li>
<li>属性:自动生成只读属性。</li>
<li>Deconstruct 方法:用于解构记录对象,对于习惯写<code>TS</code>的小伙伴相当友好。</li>
<li>Equals 和 GetHashCode 方法:基于属性值的相等性比较。</li>
<li>ToString 方法:提供友好的字符串表示,对于调试输出特别友好。</li>
</ul>
<ol start="3">
<li>基于值的相等性语法.</li>
</ol>
<p>我们很多时候有这种需求就是比较一个类的所有属性来判断逻辑.如果使用 record 的话 我们只需要<code>==</code>或者<code>Equals</code>就能判断,</p>
<ol start="4">
<li>非破坏性复制值</li>
</ol>
<p>对于一个 class 的浅表复制,我们可能需要实现<code>ICloneable</code>,亦或者 new 一个对象逐个属性赋值,当然还有其他的方法,但是呢肯定是没有 record 来的这么简单直接.我们仅需要一个<code>with</code>关键字就干完了</p>
<pre><code class="language-csharp">public record Person(string FirstName, string LastName, int Age);
var person1 = new Person("vip", "wan", 18);
var person2 = person1 with { Age = 30 };
Console.WriteLine(person1); // 输出: Person { FirstName = vip, LastName = wan, Age = 18 }
Console.WriteLine(person2); // 输出: Person { FirstName = vip, LastName = wan, Age = 30 }
</code></pre>
<p>在单元测试中的场景:</p>
<pre><code class="language-csharp">public readonly record struct RegexMatch(string Raw, string Act);
public void Test_Regex_A_Href(RegexMatch rm)
{
//获取a标签的href属性的表达式
var regex = new Regex(@"<a[^>]+href=(['""])(?<href>.*?)\1[^>]*>", RegexOptions.IgnoreCase);
var match = regex.Match(rm.Raw);
Assert.True(match.Success);
Assert.Equal(rm.Act, match.Groups["href"].Value);
}
public class AHrefTestData : TheoryData<RegexMatch>
{
public AHrefTestData()
{
var rm = new RegexMatch("<a href='http://www.baidu.com'></a>", "http://www.baidu.com");
Add(rm);
//因为期待的结果都是一样的,所以可以直接使用with语法节省编码量
Add(rm with { Raw = "<a href=\"http://www.baidu.com\"></a>" });
Add(rm with { Raw = "<a href='http://www.baidu.com'>" });
Add(rm with { Raw = "<a Href=\"http://www.baidu.com\">" });
}
}
</code></pre>
<ol start="5">
<li>解构的支持</li>
</ol>
<p>record 类型自动生成 Deconstruct 方法,允许你轻松地解构 record 对象,对于全栈的同学书写就是手到擒来!</p>
<pre><code class="language-csharp">var person = new Person("vip", "wan", 18);
var (firstName, lastName, age) = person;
Console.WriteLine(firstName); // 输出: vip
Console.WriteLine(lastName);// 输出: wan
Console.WriteLine(age); // 输出: 18
</code></pre>
<ol start="6">
<li>结合模式匹配</li>
</ol>
<p>record 类型与模式匹配功能很好地集成在一起,使得在模式匹配中使用 record 对象更加方便。</p>
<pre><code class="language-csharp">public record Person(string UserName, int Age);
public string GetPersonInfo(Person person) => person switch
{
{ Age: < 18 } => "Minor",
{ Age: >= 18 } => "Adult",
_ => "Unknown"
};
</code></pre>
<ol start="7">
<li>填充既有类</li>
</ol>
<p>嗯当前 C# 语言是真的突飞猛进,年底就要发布<code>C# 13</code>了,小伙伴们都<s>直呼学不动了</s>!,当然也有同学肯定也尝鲜了<code>主构造函数</code>了吧, 如果想要对主构造函数进一步了解可以 点击链接 对于注入的服务又能少撸不少的代码!</p>
<p>那么既然 class 都有了<code>主构造函数</code>,是不是意味着 record 就失去意义了呢?!,嗯?!你忘了上面的那些糖的<strong>甜度</strong>了吗?</p>
<p>因此我们如果需要对既有的 class 支持到 record 的特性我们只需要在class前加上 record 即可.</p>
<pre><code class="language-csharp">public record class User {
public string UserName{ get; set;}
public int Age { get; set;}
}
var user1 = new User { UserName = "vipwan" , Age = 18};
var user2 = user1 with { };
var user3 = user1 with { Age = 30 };
user1 == user2 // true;
user3.ToString() // "User { Name = vipwan, Age = 30 }"
</code></pre>
<ol start="8">
<li><code>C# 10</code> 提供的<code>record struct</code>,<code>readonly record struct</code>支持:</li>
</ol>
<p>默认情况下编译器将<code>record</code>等价于<code>record class</code>,record class由于是基于class的封装因此完整的继承了class的多态性等特征,两者复制比较 <strong>编译器内部实现代码</strong>是不同的,struct 的性能会稍好(值类型和引用类型的主要区别),因此<strong>MS</strong>在 <strong>C# 10</strong>中带来了<code>(readonly) record struct</code>的支持;</p>
<p>对于<code>readonly record struct</code>和<code>record struct</code>的区别:</p>
<ul>
<li>record struct:默认不可变,但可以包含可变字段和属性,适用于需要一定可变性的值类型数据结构。</li>
<li>readonly record struct:由于其完全不可变性,编译器可以进行更多的优化,例如避免不必要的复制,从而<strong>提高性能</strong>。</li>
</ul>
<p>对于代码的区别请看:</p>
<pre><code class="language-csharp">//本身是class所以完整的支持继承和多态
public abstract record PersonBase(string FirstName, string LastName);
public record Person(string FirstName, string LastName, string Address) : PersonBase(FirstName, LastName)
{
public int? Age { get; set; }
}
//由于PersonStruct本身是struct因此不能被继承
public record struct PersonStruct(string FirstName, string LastName)
{
public int? Age { get; set; }
}
//Age只能只读或者init;因此不能设置为:public int? Age { get; set; }
public readonly record struct PersonReadonlyStruct(string FirstName, string LastName)
{
public int? Age { get; init; }
}
</code></pre>
<p>对于如何选择总结一句:</p>
<blockquote>
<p>struct 极致性能, class 包容性强,对于大多数情况下 (readonly) record struct 够用;对于包容免除后顾之忧,优先选择 record class !</p>
</blockquote>
<h3 id="总结">总结</h3>
<p>使用 record 类型的主要好处包括简洁的语法、自动生成的成员、基于值的相等性、非破坏性复制、解构支持、继承支持和与模式匹配的良好集成。这些特性使得 record 类型非常适合用于不可变数据对象(DTO,VO等),提高了代码的<strong>可读性</strong>、<strong>可维护性</strong>和<strong>开发效率</strong>。</p><br><br>
来源:https://www.cnblogs.com/vipwan/p/18325508
頁:
[1]