何时使用记录 vs 类 vs 结构 [英] When to use record vs class vs struct

查看:27
本文介绍了何时使用记录 vs 类 vs 结构的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

  • 我是否应该将 Record 用于在控制器和服务层之间移动数据的所有 DTO 类?

  • Should I be using Record for all of my DTO classes which move data between controller and service layer ?

我是否应该将 Record 用于我的所有请求绑定,因为理想情况下我希望发送到控制器的请求对于我的 asp.net api 来说是不可变的

Should I be using Record for all my request bindings since ideally I would want the request sent to the controller be immutable for my asp.net api

什么是记录?https://anthonygiretti.com/2020/06/17/introducing-c-9-records/

  public class HomeController 
  { 
    public IHttpAction Search([FromBody] SearchParameters searchParams)
    {
       _service.Search(searchParams);
    }
  }

是否应该将SearchParameters 设为Record?

推荐答案

短版

您的数据类型可以是类型吗?使用 struct.不?您的类型是否描述了类似值的,最好是不可变的状态?使用记录.

Short version

Can your data type be a value type? Go with struct. No? Does your type describe a value-like, preferably immutable state? Go with record.

否则使用 class.所以...

Use class otherwise. So...

  1. 是的,如果是单向流程,请将 record 用于 DTO.
  2. 是的,不可变的请求绑定是记录
  3. 的理想用例
  4. 是的,SearchParameters记录 的理想用例.
  1. Yes, use records for your DTOs if it is one way flow.
  2. Yes, immutable request bindings are an ideal user case for a record
  3. Yes, SearchParameters are an ideal user case for a record.

有关record 使用的更多实际示例,您可以查看此repo.

For further practical examples of record use, you can check this repo.

structclassrecord 是用户数据类型.

结构是值类型.类是引用类型.记录是默认不可变的引用类型.

Structures are value types. Classes are reference types. Records are by default immutable reference types.

当您需要某种层次结构来描述您的数据类型时,例如继承或指向另一个 structstruct 或基本上指向的事物其他东西,你需要一个引用类型.

When you need some sort of hierarchy to describe your data types like inheritance or a struct pointing to another struct or basically things pointing to other things, you need a reference type.

当您希望您的类型默认面向值时,记录解决了这个问题.记录是引用类型,但具有面向值的语义.

Records solve the problem when you want your type to be a value oriented by default. Records are reference types but with the value oriented semantic.

话虽如此,问自己这些问题...

With that being said, ask yourself these questions...

您的数据类型是否尊重所有这些规则:

  1. 它在逻辑上表示单个值,类似于原始类型(int、double 等).
  2. 它的实例大小低于 16 字节.
  3. 它是不可变的.
  4. 不必经常装箱.

  • 是吗?它应该是一个struct.
  • 没有?它应该是某种引用类型.
  • 您的数据类型是否封装了某种复杂的值?值是不可变的吗?您是否在单向(单向)流中使用它?

    Does your data type encapsulate some sort of a complex value? Is the value immutable? Do you use it in unidirectional (one way) flow?

    • 是吗?使用 record.
    • 没有?使用 class.

    顺便说一句:不要忘记 匿名对象.C# 10.0 中会有匿名记录.

    BTW: Don't forget about anonymous objects. There will be an anonymous records in C# 10.0.

    一个记录实例可以是可变的,如果你使它可变.

    A record instance can be mutable if you make it mutable.

    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# 编译器发出的特殊 clone 方法创建.值类型成员被复制并装箱.引用类型成员指向相同的引用.当且仅当记录仅具有值类型属性时,您才能对记录进行深层复制.记录的任何引用类型成员属性都被复制为浅拷贝.


    An assignment of a record is a shallow copy of the record. A copy by with expression of a record is neither a shallow nor a deep copy. The copy is created by a special clone method emitted by C# compiler. Value-type members are copied and boxed. Reference-type members are pointed to the same reference. You can do a deep copy of a record if and only if the record has value type properties only. Any reference type member property of a record is copied as a shallow copy.

    请参阅此示例(使用 C# 9.0 中的顶级功能):

    See this example (using top-level feature in 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
    }
    

    这里的性能损失是显而易见的.要在您拥有的记录实例中复制更大的数据,您将获得更大的性能损失.通常,您应该创建小型、纤细的类,此规则也适用于记录.

    The performance penalty is obvious here. A larger data to copy in a record instance you have, a larger performance penalty you get. Generally, you should create small, slim classes and this rule applies to records too.

    如果您的应用程序使用数据库或文件系统,我不会太担心这种惩罚.数据库/文件系统操作通常较慢.

    If your application is using database or file system, I wouldn't worry about this penalty much. The database/file system operations are generally slower.

    我做了一些综合测试(下面的完整代码),其中类很受欢迎,但在实际应用中,影响应该是不明显的.

    I made some synthetic test (full code below) where classes are wining but in real life application, the impact should be unnoticeable.

    此外,性能并不总是第一要务.如今,代码的可维护性和可读性比高度优化的意大利面条式代码更可取.他更喜欢哪种方式是代码作者的选择.

    In addition, the performance is not always number one priority. These days, the maintainability and readability of your code is preferable than highly optimized spaghetti code. It is the code author choice which way (s)he would prefer.

    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
    
    
    

    <头>
    方法平均错误StdDev
    测试记录120.19 μs2.299 μs2.150 μs
    测试类98.91 μs0.856 μs0.800 μs

    这篇关于何时使用记录 vs 类 vs 结构的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆