.net-5 - 何时使用记录 vs 类 vs 结构
问题描述
我应该使用
Record
所有在控制器和服务层之间移动数据的 DTO 类吗?我是否应该使用我
Record
的所有请求绑定,因为理想情况下我希望发送到控制器的请求对于我的 asp.net API 是不可变的
什么是记录?Anthony Giretti 介绍 C# 9:记录
public class HomeController
{
public IHttpAction Search([FromBody] SearchParameters searchParams)
{
_service.Search(searchParams);
}
}
应该SearchParameters
发一个Record
?
解决方案
简洁版本
你的数据类型可以是值类型吗?一起去struct
。不?您的类型是否描述了类似值的、最好是不可变的状态?一起去record
。
class
否则使用。所以...
- 是的,
record
如果它是单向流,则将 s 用于您的 DTO。 - 是的,不可变的请求绑定是一个理想的用户案例
record
- 是
SearchParameters
的,是record
.
有关更多实际record
使用示例,您可以查看此repo。
长版
A struct
、aclass
和 arecord
是用户数据类型。
结构是值类型。类是引用类型。记录默认是不可变的引用类型。
当您需要某种层次结构来描述您的数据类型(例如继承或struct
指向另一个struct
或基本上指向其他事物的事物)时,您需要一个引用类型。
当您希望您的类型默认为面向值时,记录解决了这个问题。记录是引用类型,但具有面向值的语义。
话虽如此,问自己这些问题......
您的数据类型是否遵守所有这些规则:
- 它在逻辑上表示单个值,类似于原始类型(int、double 等)。
- 它的实例大小小于 16 个字节。
- 它是不可变的。
- 它不必经常装箱。
- 是的?它应该是一个
struct
. - 不?它应该是某种引用类型。
您的数据类型是否封装了某种复杂的值?价值是不变的吗?您是否在单向(单向)流中使用它?
- 是的?一起去
record
。 - 不?一起去
class
。
顺便说一句:不要忘记匿名对象。C# 10.0 中会有匿名记录。
笔记
如果将记录实例设为可变,则它可以是可变的。
class Program
{
static void Main()
{
var test = new Foo("a");
Console.WriteLine(test.MutableProperty);
test.MutableProperty = 15;
Console.WriteLine(test.MutableProperty);
//test.Bar = "new string"; // will not compile
}
}
public record Foo(string Bar)
{
public double MutableProperty { get; set; } = 10.0;
}
记录的分配是记录的浅拷贝。记录的表达式复制with
既不是浅拷贝也不是深拷贝。该副本由 C# 编译器发出的特殊克隆方法创建。值类型成员被复制和装箱。引用类型成员指向同一个引用。当且仅当记录仅具有值类型属性时,您才能对记录进行深层复制。记录的任何引用类型成员属性都被复制为浅拷贝。
请参阅此示例(使用 C# 9.0 中的顶级功能):
using System.Collections.Generic;
using static System.Console;
var foo = new SomeRecord(new List<string>());
var fooAsShallowCopy = foo;
var fooAsWithCopy = foo with { }; // A syntactic sugar for new SomeRecord(foo.List);
var fooWithDifferentList = foo with { List = new List<string>() { "a", "b" } };
var differentFooWithSameList = new SomeRecord(foo.List); // This is the same like foo with { };
foo.List.Add("a");
WriteLine($"Count in foo: {foo.List.Count}"); // 1
WriteLine($"Count in fooAsShallowCopy: {fooAsShallowCopy.List.Count}"); // 1
WriteLine($"Count in fooWithDifferentList: {fooWithDifferentList.List.Count}"); // 2
WriteLine($"Count in differentFooWithSameList: {differentFooWithSameList.List.Count}"); // 1
WriteLine($"Count in fooAsWithCopy: {fooAsWithCopy.List.Count}"); // 1
WriteLine("");
WriteLine($"Equals (foo & fooAsShallowCopy): {Equals(foo, fooAsShallowCopy)}"); // True. The lists inside are the same.
WriteLine($"Equals (foo & fooWithDifferentList): {Equals(foo, fooWithDifferentList)}"); // False. The lists are different
WriteLine($"Equals (foo & differentFooWithSameList): {Equals(foo, differentFooWithSameList)}"); // True. The list are the same.
WriteLine($"Equals (foo & fooAsWithCopy): {Equals(foo, fooAsWithCopy)}"); // True. The list are the same, see below.
WriteLine($"ReferenceEquals (foo.List & fooAsShallowCopy.List): {ReferenceEquals(foo.List, fooAsShallowCopy.List)}"); // True. The records property points to the same reference.
WriteLine($"ReferenceEquals (foo.List & fooWithDifferentList.List): {ReferenceEquals(foo.List, fooWithDifferentList.List)}"); // False. The list are different instances.
WriteLine($"ReferenceEquals (foo.List & differentFooWithSameList.List): {ReferenceEquals(foo.List, differentFooWithSameList.List)}"); // True. The records property points to the same reference.
WriteLine($"ReferenceEquals (foo.List & fooAsWithCopy.List): {ReferenceEquals(foo.List, fooAsWithCopy.List)}"); // True. The records property points to the same reference.
WriteLine("");
WriteLine($"ReferenceEquals (foo & fooAsShallowCopy): {ReferenceEquals(foo, fooAsShallowCopy)}"); // True. !!! fooAsCopy is pure shallow copy of foo. !!!
WriteLine($"ReferenceEquals (foo & fooWithDifferentList): {ReferenceEquals(foo, fooWithDifferentList)}"); // False. These records are two different reference variables.
WriteLine($"ReferenceEquals (foo & differentFooWithSameList): {ReferenceEquals(foo, differentFooWithSameList)}"); // False. These records are two different reference variables and reference type property hold by these records does not matter in ReferenceEqual.
WriteLine($"ReferenceEquals (foo & fooAsWithCopy): {ReferenceEquals(foo, fooAsWithCopy)}"); // False. The same story as differentFooWithSameList.
WriteLine("");
var bar = new RecordOnlyWithValueNonMutableProperty(0);
var barAsShallowCopy = bar;
var differentBarDifferentProperty = bar with { NonMutableProperty = 1 };
var barAsWithCopy = bar with { };
WriteLine($"Equals (bar & barAsShallowCopy): {Equals(bar, barAsShallowCopy)}"); // True.
WriteLine($"Equals (bar & differentBarDifferentProperty): {Equals(bar, differentBarDifferentProperty)}"); // False. Remember, the value equality is used.
WriteLine($"Equals (bar & barAsWithCopy): {Equals(bar, barAsWithCopy)}"); // True. Remember, the value equality is used.
WriteLine($"ReferenceEquals (bar & barAsShallowCopy): {ReferenceEquals(bar, barAsShallowCopy)}"); // True. The shallow copy.
WriteLine($"ReferenceEquals (bar & differentBarDifferentProperty): {ReferenceEquals(bar, differentBarDifferentProperty)}"); // False. Operator with creates a new reference variable.
WriteLine($"ReferenceEquals (bar & barAsWithCopy): {ReferenceEquals(bar, barAsWithCopy)}"); // False. Operator with creates a new reference variable.
WriteLine("");
var fooBar = new RecordOnlyWithValueMutableProperty();
var fooBarAsShallowCopy = fooBar; // A shallow copy, the reference to bar is assigned to barAsCopy
var fooBarAsWithCopy = fooBar with { }; // A deep copy by coincidence because fooBar has only one value property which is copied into barAsDeepCopy.
WriteLine($"Equals (fooBar & fooBarAsShallowCopy): {Equals(fooBar, fooBarAsShallowCopy)}"); // True.
WriteLine($"Equals (fooBar & fooBarAsWithCopy): {Equals(fooBar, fooBarAsWithCopy)}"); // True. Remember, the value equality is used.
WriteLine($"ReferenceEquals (fooBar & fooBarAsShallowCopy): {ReferenceEquals(fooBar, fooBarAsShallowCopy)}"); // True. The shallow copy.
WriteLine($"ReferenceEquals (fooBar & fooBarAsWithCopy): {ReferenceEquals(fooBar, fooBarAsWithCopy)}"); // False. Operator with creates a new reference variable.
WriteLine("");
fooBar.MutableProperty = 2;
fooBarAsShallowCopy.MutableProperty = 3;
fooBarAsWithCopy.MutableProperty = 3;
WriteLine($"fooBar.MutableProperty = {fooBar.MutableProperty} | fooBarAsShallowCopy.MutableProperty = {fooBarAsShallowCopy.MutableProperty} | fooBarAsWithCopy.MutableProperty = {fooBarAsWithCopy.MutableProperty}"); // fooBar.MutableProperty = 3 | fooBarAsShallowCopy.MutableProperty = 3 | fooBarAsWithCopy.MutableProperty = 3
WriteLine($"Equals (fooBar & fooBarAsShallowCopy): {Equals(fooBar, fooBarAsShallowCopy)}"); // True.
WriteLine($"Equals (fooBar & fooBarAsWithCopy): {Equals(fooBar, fooBarAsWithCopy)}"); // True. Remember, the value equality is used. 3 != 4
WriteLine($"ReferenceEquals (fooBar & fooBarAsShallowCopy): {ReferenceEquals(fooBar, fooBarAsShallowCopy)}"); // True. The shallow copy.
WriteLine($"ReferenceEquals (fooBar & fooBarAsWithCopy): {ReferenceEquals(fooBar, fooBarAsWithCopy)}"); // False. Operator with creates a new reference variable.
WriteLine("");
fooBarAsWithCopy.MutableProperty = 4;
WriteLine($"fooBar.MutableProperty = {fooBar.MutableProperty} | fooBarAsShallowCopy.MutableProperty = {fooBarAsShallowCopy.MutableProperty} | fooBarAsWithCopy.MutableProperty = {fooBarAsWithCopy.MutableProperty}"); // fooBar.MutableProperty = 3 | fooBarAsShallowCopy.MutableProperty = 3 | fooBarAsWithCopy.MutableProperty = 4
WriteLine($"Equals (fooBar & fooBarAsWithCopy): {Equals(fooBar, fooBarAsWithCopy)}"); // False. Remember, the value equality is used. 3 != 4
WriteLine("");
var venom = new MixedRecord(new List<string>(), 0); // Reference/Value property, mutable non-mutable.
var eddieBrock = venom;
var carnage = venom with { };
venom.List.Add("I'm a predator.");
carnage.List.Add("All I ever wanted in this world is a carnage.");
WriteLine($"Count in venom: {venom.List.Count}"); // 2
WriteLine($"Count in eddieBrock: {eddieBrock.List.Count}"); // 2
WriteLine($"Count in carnage: {carnage.List.Count}"); // 2
WriteLine($"Equals (venom & eddieBrock): {Equals(venom, eddieBrock)}"); // True.
WriteLine($"Equals (venom & carnage): {Equals(venom, carnage)}"); // True. Value properties has the same values, the List property points to the same reference.
WriteLine($"ReferenceEquals (venom & eddieBrock): {ReferenceEquals(venom, eddieBrock)}"); // True. The shallow copy.
WriteLine($"ReferenceEquals (venom & carnage): {ReferenceEquals(venom, carnage)}"); // False. Operator with creates a new reference variable.
WriteLine("");
eddieBrock.MutableList = new List<string>();
eddieBrock.MutableProperty = 3;
WriteLine($"Equals (venom & eddieBrock): {Equals(venom, eddieBrock)}"); // True. Reference or value type does not matter. Still a shallow copy of venom, still true.
WriteLine($"Equals (venom & carnage): {Equals(venom, carnage)}"); // False. the venom.List property does not points to the same reference like in carnage.List anymore.
WriteLine($"ReferenceEquals (venom & eddieBrock): {ReferenceEquals(venom, eddieBrock)}"); // True. The shallow copy.
WriteLine($"ReferenceEquals (venom & carnage): {ReferenceEquals(venom, carnage)}"); // False. Operator with creates a new reference variable.
WriteLine($"ReferenceEquals (venom.List & carnage.List): {ReferenceEquals(venom.List, carnage.List)}"); // True. Non mutable reference type.
WriteLine($"ReferenceEquals (venom.MutableList & carnage.MutableList): {ReferenceEquals(venom.MutableList, carnage.MutableList)}"); // False. This is why Equals(venom, carnage) returns false.
WriteLine("");
public record SomeRecord(List<string> List);
public record RecordOnlyWithValueNonMutableProperty(int NonMutableProperty);
public record RecordOnlyWithValueMutableProperty
{
public int MutableProperty { get; set; } = 1; // this property gets boxed
}
public record MixedRecord(List<string> List, int NonMutableProperty)
{
public List<string> MutableList { get; set; } = new();
public int MutableProperty { get; set; } = 1; // this property gets boxed
}
性能损失在这里很明显。要在您拥有的记录实例中复制更大的数据,您将获得更大的性能损失。通常,您应该创建小型、精简的类,并且此规则也适用于记录。
如果您的应用程序使用数据库或文件系统,我不会太担心这种惩罚。数据库/文件系统操作通常较慢。
我做了一些综合测试(下面的完整代码),其中类正在获胜,但在现实生活中的应用程序中,影响应该是不明显的。
此外,性能并不总是第一要务。如今,您的代码的可维护性和可读性比高度优化的意大利面条代码更可取。这是代码作者选择他更喜欢的方式。
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
namespace SmazatRecord
{
class Program
{
static void Main()
{
var summary = BenchmarkRunner.Run<Test>();
}
}
public class Test
{
[Benchmark]
public int TestRecord()
{
var foo = new Foo("a");
for (int i = 0; i < 10000; i++)
{
var bar = foo with { Bar = "b" };
bar.MutableProperty = i;
foo.MutableProperty += bar.MutableProperty;
}
return foo.MutableProperty;
}
[Benchmark]
public int TestClass()
{
var foo = new FooClass("a");
for (int i = 0; i < 10000; i++)
{
var bar = new FooClass("b")
{
MutableProperty = i
};
foo.MutableProperty += bar.MutableProperty;
}
return foo.MutableProperty;
}
}
public record Foo(string Bar)
{
public int MutableProperty { get; set; } = 10;
}
public class FooClass
{
public FooClass(string bar)
{
Bar = bar;
}
public int MutableProperty { get; set; }
public string Bar { get; }
}
}
结果:
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18363.1379 (1909/November2018Update/19H2)
AMD FX(tm)-8350, 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=5.0.103
[Host] : .NET Core 5.0.3 (CoreCLR 5.0.321.7212, CoreFX 5.0.321.7212), X64 RyuJIT
DefaultJob : .NET Core 5.0.3 (CoreCLR 5.0.321.7212, CoreFX 5.0.321.7212), X64 RyuJIT
方法 | 意思是 | 错误 | 标准差 |
---|---|---|---|
测试记录 | 120.19 微秒 | 2.299 微秒 | 2.150 微秒 |
测试类 | 98.91 微秒 | 0.856 微秒 | 0.800 微秒 |
推荐阅读
- email - 没有在odoo自己的自定义模板中获取完整的URL?
- c - 尝试查找数组的总和时得到一个垃圾值
- angular - 将参数传递给 mat-dialog open 方法
- javascript - 面临错误 (0 , (_wordwrap || _load_wordwrap(...)).default)(...)(...).trimStart 不是 expo init 上的函数
- python-3.x - 如何将一个列表中的项目添加到另一个列表中?
- amazon-web-services - 等效于 Digital Ocean 中的 AWS 安全组
- vue.js - 如何使用 Vue watch 来观察一个表单项的值?
- c# - 从 Angular HttpClient 向 ASP.NET Core 2.2 Web API(启用 CORS)进行 POST 调用时出现 404 错误
- python - 将 Keras 张量拆分为列表
- javascript - 删除除首先使用纯 JS 之外的所有类