关于C#:何时使用结构struct?

When to use struct?

什么时候应该使用C中的struct而不是class?我的概念模型是,当项只是值类型的集合时,会使用结构。一种逻辑上把它们结合成一个整体的方法。

我在这里遇到了这些规则:

  • 结构应表示单个价值。
  • 结构应具有内存占用空间小于16字节。
  • 之后不应更改结构创造。

这些规则有效吗?结构在语义上是什么意思?


OP引用的源代码有一定的可信度……但是微软呢——在结构使用上的立场是什么?我向微软寻求额外的学习,我发现:好的。

Consider defining a structure instead of a class if instances of the
type are small and commonly short-lived or are commonly embedded in
other objects.

Ok.

Do not define a structure unless the type has all of the following characteristics:

Ok.

  • It logically represents a single value, similar to primitive types (integer, double, and so on).
  • It has an instance size smaller than 16 bytes.
  • It is immutable.
  • It will not have to be boxed frequently.
  • 微软一贯违反这些规则

    好的,2和3无论如何。我们心爱的字典有两个内部结构:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    [StructLayout(LayoutKind.Sequential)]  // default for structs
    private struct Entry  //<Tkey, TValue>
    {
        //  View code at *Reference Source
    }

    [Serializable, StructLayout(LayoutKind.Sequential)]
    public struct Enumerator :
        IEnumerator<KeyValuePair<TKey, TValue>>, IDisposable,
        IDictionaryEnumerator, IEnumerator
    {
        //  View code at *Reference Source
    }

    *参考源好的。

    "jonnycantcode.com"的源代码是4个中的3个,这是可以原谅的,因为4可能不是一个问题。如果你发现自己在拳击一个结构,重新考虑你的架构。好的。

    让我们看看为什么微软会使用这些结构:好的。

  • 每个结构(EntryEnumerator表示单个值。
  • 速度
  • Entry从未作为Dictionary类之外的参数传递。进一步的调查表明,为了满足IEnumerable的实现,字典使用每次请求枚举器时都会复制的Enumerator结构……这是有意义的。
  • 字典类内部。Enumerator是公共的,因为字典是可枚举的,必须具有与IEnumerator接口实现(例如IEnumerator getter)相同的可访问性。
  • 更新—另外,要认识到当结构实现接口时(就像枚举器那样),并被转换为该实现的类型时,该结构将成为引用类型并移动到堆中。在Dictionary类内部,枚举器仍然是值类型。但是,只要方法调用GetEnumerator(),就会返回引用类型IEnumerator。好的。

    我们在这里没有看到任何尝试或需求证明来保持结构不变或仅维持16字节或更小的实例大小:好的。

  • 上面结构中的任何内容都不能声明为readonly—不可变
  • 这些结构的大小可以超过16个字节
  • Entry的寿命不确定(从Add()Remove()Clear()或垃圾收集);
  • 还有…4。两个结构都存储tkey和tvalue,我们都知道它们非常能够作为引用类型(添加了额外的信息)好的。

    尽管有哈希键,字典还是很快的,部分原因是实例结构比引用类型更快。这里,我有一个Dictionary,它存储了300000个随机整数和顺序递增的键。好的。

    Capacity: 312874
    MemSize: 2660827 bytes
    Completed Resize: 5ms
    Total time to fill: 889ms

    Ok.

    容量:必须调整内部数组大小之前可用的元素数。好的。

    Memsize:通过将字典序列化为一个memorystream并获取一个字节长度(对于我们的目的来说足够精确)来确定。好的。

    已完成调整大小:将内部数组从150862个元素调整为312874个元素所需的时间。当您发现每个元素都是通过Array.CopyTo()顺序复制的时,这并不太糟糕。好的。

    填充总时间:由于日志记录和添加到源中的OnResize事件,无疑是倾斜的;但是,在操作期间,在调整15次大小的同时填充30万个整数仍然令人印象深刻。只是出于好奇,如果我已经知道容量,总的填充时间是多少?13MS好的。

    那么,现在,如果Entry是一个类呢?这些时间或指标真的有那么大的不同吗?好的。

    Capacity: 312874
    MemSize: 2660827 bytes
    Completed Resize: 26ms
    Total time to fill: 964ms

    Ok.

    显然,最大的区别在于调整大小。如果字典是用容量初始化的,有什么区别吗?不够关心…12ms。好的。

    因为Entry是一个结构,所以它不需要像引用类型那样初始化。这既是价值类型的美,也是其祸害。为了使用Entry作为引用类型,我必须插入以下代码:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    /*
     *  Added to satisfy initialization of entry elements --
     *  this is where the extra time is spent resizing the Entry array
     * **/

    for (int i = 0 ; i < prime ; i++)
    {
        destinationArray[i] = new Entry( );
    }
    /*  *********************************************** */

    我必须初始化Entry的每个数组元素作为引用类型的原因可以在msdn:structure design中找到。简而言之:好的。

    Do not provide a default constructor for a structure.

    Ok.

    If a structure defines a default constructor, when arrays of the
    structure are created, the common language runtime automatically
    executes the default constructor on each array element.

    Ok.

    Some compilers, such as the C# compiler, do not allow structures to
    have default constructors.

    Ok.

    这实际上相当简单,我们将借鉴阿西莫夫的机器人三定律:好的。

  • 结构必须安全才能使用
  • 结构必须有效地执行其功能,除非这将违反规则1
  • 结构在使用过程中必须保持完整,除非其破坏需要满足规则1
  • …我们应该从中得到什么:简而言之,对值类型的使用负责。它们快速高效,但如果维护不当(即无意复制),则能够导致许多意外行为。好的。好啊。


    每当您不需要多态性时,都需要值语义,并且希望避免堆分配和相关的垃圾收集开销。然而,需要注意的是,结构(任意大的)传递比类引用(通常是一个机器字)更昂贵,因此类最终可能在实践中更快。


    我不同意原邮件中给出的规则。以下是我的规则:

    1)当存储在数组中时,使用结构来提高性能。(另请参见何时结构是答案?)

    2)您需要在代码中向C/C++中传递结构化数据

    3)除非需要,否则不要使用结构:

    • 它们的行为不同于"普通对象"(引用类型),并且当作为参数传递时,会导致意外行为;如果看代码的人看了代码,这就特别危险了不知道他们正在处理结构。
    • 它们不能被继承。
    • 将结构作为参数传递比类更昂贵。


    当需要值语义而不是引用语义时,请使用结构。

    编辑

    不知道为什么人们会反对这一点,但这是一个有效的观点,并且是在OP澄清他的问题之前提出的,这是结构最基本的原因。

    如果需要引用语义,则需要类而不是结构。


    除了"它是一个值"的答案之外,使用结构的一个特定场景是,当您知道您有一组导致垃圾收集问题的数据,并且您有许多对象时。例如,人员实例的大列表/数组。这里的自然隐喻是一个类,但是如果您有大量的长寿者实例,它们最终会阻塞gen-2并导致gc暂停。如果场景允许,这里的一个潜在方法是使用人员结构的数组(而不是列表),即Person[]。现在,与在Gen-2中拥有数百万个对象不同,您在LOH上拥有一个单独的块(这里我假设没有字符串等,即一个没有任何引用的纯值)。这对GC的影响很小。

    使用此数据很难,因为对于结构来说,数据的大小可能过大,而且您不想一直复制fat值。但是,直接在数组中访问它并不会复制结构-它已经就位(与列表索引器不同,列表索引器会进行复制)。这意味着要对索引进行大量的工作:

    1
    2
    int index = ...
    int id = peopleArray[index].Id;

    请注意,保持值本身不变将在这里有所帮助。对于更复杂的逻辑,使用带有by-ref参数的方法:

    1
    2
    3
    void Foo(ref Person person) {...}
    ...
    Foo(ref peopleArray[index]);

    同样,这是正确的-我们没有复制值。

    在非常具体的场景中,这种策略可能非常成功;但是,它是一种相当高级的scernario,只有在您知道自己在做什么和为什么的情况下才应该尝试。这里的默认值是一个类。


    从C语言规范:

    1.7 Structs

    Like classes, structs are data structures that can contain data members and function members, but unlike classes, structs are
    value types and do not require heap allocation. A variable of a struct
    type directly stores the data of the struct, whereas a variable of a
    class type stores a reference to a dynamically allocated object.
    Struct types do not support user-specified inheritance, and all struct
    types implicitly inherit from type object.

    Structs are particularly useful for small data structures that have
    value semantics. Complex numbers, points in a coordinate system, or
    key-value pairs in a dictionary are all good examples of structs. The
    use of structs rather than classes for small data structures can make
    a large difference in the number of memory allocations an application
    performs. For example, the following program creates and initializes
    an array of 100 points. With Point implemented as a class, 101
    separate objects are instantiated—one for the array and one each for
    the 100 elements.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class Point
    {
       public int x, y;

       public Point(int x, int y) {
          this.x = x;
          this.y = y;
       }
    }

    class Test
    {
       static void Main() {
          Point[] points = new Point[100];
          for (int i = 0; i < 100; i++) points[i] = new Point(i, i);
       }
    }

    另一种方法是使点成为一个结构。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    struct Point
    {
       public int x, y;

       public Point(int x, int y) {
          this.x = x;
          this.y = y;
       }
    }

    现在,只有一个对象被实例化,数组中的一个对象和点实例被直接存储在数组中。

    结构构造函数是用新的运算符调用的,但这并不意味着正在分配内存。结构构造函数不动态地分配对象并返回对它的引用,而是简单地返回结构值本身(通常位于堆栈上的临时位置),然后根据需要复制该值。

    对于类,两个变量可以引用同一个对象,因此一个变量上的操作可能影响另一个变量引用的对象。对于结构,每个变量都有自己的数据副本,并且一个变量上的操作不可能影响另一个变量。例如,以下代码片段生成的输出取决于点是类还是结构。

    1
    2
    3
    4
    Point a = new Point(10, 10);
    Point b = a;
    a.x = 20;
    Console.WriteLine(b.x);

    如果point是一个类,则输出为20,因为a和b引用相同的对象。如果point是结构,则输出为10,因为a到b的赋值创建了值的副本,并且该副本不受随后对a.x赋值的影响。

    前一个示例强调了结构的两个限制。首先,复制整个结构通常比复制对象引用效率低,因此使用结构传递赋值和值参数传递可能比使用引用类型更昂贵。其次,除了引用和输出参数外,不可能创建对结构的引用,这会排除在许多情况下使用结构的可能性。


    结构有利于数据的原子表示,在这种情况下,代码可以多次复制所述数据。克隆一个对象通常比复制一个结构要昂贵,因为它涉及到分配内存、运行构造函数以及在完成后释放/垃圾回收。


    这是一个基本规则。

    • 如果所有成员字段都是值类型,则创建一个结构。

    • 如果任何一个成员字段是引用类型,请创建一个类。这是因为引用类型字段无论如何都需要堆分配。

    Expple

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public struct MyPoint
    {
        public int X; // Value Type
        public int Y; // Value Type
    }

    public class MyPointWithName
    {
        public int X; // Value Type
        public int Y; // Value Type
        public string Name; // Reference Type
    }


    第一:互操作方案,或者需要指定内存布局时

    第二:当数据与参考指针的大小几乎相同时。


    在需要使用structlayoutattribute(通常用于pinvoke)显式指定内存布局的情况下,需要使用"struct"。

    edit:comment指出可以使用带有structlayoutattribute的类或结构,这当然是正确的。实际上,您通常会使用一个结构——它是在堆栈上分配的,而不是在堆上分配的,如果您只是向非托管方法调用传递一个参数,这是有意义的。


    我使用结构来打包或解包任何类型的二进制通信格式。这包括读取或写入磁盘、DirectX顶点列表、网络协议或处理加密/压缩数据。

    在这方面,你列出的三条指导原则对我没有帮助。当我需要按照特定的顺序写出四百字节的东西时,我要定义一个四百字节的结构,我要用它应该具有的任何不相关的值来填充它,我要以当时最有意义的方式来设置它。(好吧,四百个字节会很奇怪——但是当我以写Excel文件为生的时候,我一直在处理多达四十个字节的结构,因为这就是一些BIFF记录的大小。)


    除了运行时直接使用的值类型以及用于PInvoke目的的各种其他值类型之外,您应该只在2个场景中使用值类型。

  • 当您需要复制语义时。
  • 当需要自动初始化时,通常在这些类型的数组中。

  • .NET支持EDCOX1 0和EDCOX1×1(在爪哇中,只能定义引用类型)。reference types的实例在托管堆中被分配,并且在没有未完成的引用时被垃圾收集。另一方面,value types的实例被分配到stack中,因此分配的内存在其作用域结束后立即被回收。当然,value types是按值传递的,reference types是参照传递的。除System.String外,所有C基元数据类型都是值类型。

    当在类上使用结构时,

    在c_中,structsvalue types类,reference types类。您可以使用enum关键字和struct关键字在c_中创建值类型。使用value type而不是reference type将导致托管堆上的对象更少,从而导致垃圾收集器(gc)的负载更少、gc循环的频率更低,从而获得更好的性能。但是,value types也有其缺点。传递一个大的struct肯定比传递一个引用要贵,这是一个明显的问题。另一个问题是与boxing/unboxing相关的开销。如果你想知道boxing/unboxing是什么意思,请按照这些链接对boxingunboxing做一个很好的解释。除了性能之外,有时您只需要类型具有值语义,如果您只有reference types,那么实现它将非常困难(或很难看)。只有在需要复制语义或需要自动初始化时,才应该使用value types,通常在这些类型的arrays中。


    结构是值类型。如果将结构赋给新变量,则新变量将包含原始变量的副本。

    1
    2
    3
    public struct IntStruct {
        public int Value {get; set;}
    }

    执行以下操作将导致存储在内存中的结构的5个实例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var struct1 = new IntStruct() { Value = 0 }; // original
    var struct2 = struct1;  // A copy is made
    var struct3 = struct2;  // A copy is made
    var struct4 = struct3;  // A copy is made
    var struct5 = struct4;  // A copy is made

    // NOTE: A"copy" will occur when you pass a struct into a method parameter.
    // To avoid the"copy", use the ref keyword.

    // Although structs are designed to use less system resources
    // than classes.  If used incorrectly, they could use significantly more.

    类是引用类型。将类赋给新变量时,该变量包含对原始类对象的引用。

    1
    2
    3
    public class IntClass {
        public int Value {get; set;}
    }

    执行以下操作只会在内存中生成类对象的一个实例。

    1
    2
    3
    4
    5
    var class1 = new IntClass() { Value = 0 };
    var class2 = class1;  // A reference is made to class1
    var class3 = class2;  // A reference is made to class1
    var class4 = class3;  // A reference is made to class1
    var class5 = class4;  // A reference is made to class1

    结构可能会增加代码错误的可能性。如果将值对象视为可变引用对象,则当所做的更改意外丢失时,开发人员可能会感到惊讶。

    1
    2
    3
    4
    5
    var struct1 = new IntStruct() { Value = 0 };
    var struct2 = struct1;
    struct2.Value = 1;
    // At this point, a developer may be surprised when
    // struct1.Value is 0 and not 1


    C或其他.NET语言中的结构类型通常用于保存行为类似于固定大小的值组的内容。结构类型的一个有用方面是,可以通过修改保存结构类型实例的存储位置来修改该实例的字段,而不是以其他方式。以这样的方式对结构进行编码是可能的:改变任何字段的唯一方法是构造一个完整的新实例,然后使用结构赋值,通过用新实例中的值覆盖目标的所有字段来改变这些字段,但除非结构不提供创建其字段具有非默认值的实例的方法如果结构本身存储在可变位置,那么它的所有字段都是可变的。

    请注意,如果结构包含私有类类型字段,并将其自己的成员重定向到包装类对象的成员,则可以设计结构类型,使其本质上表现为类类型。例如,PersonCollection可以提供SortedByNameSortedById属性,这两个属性都具有对PersonCollection的"不变"引用(在其构造函数中设置),并通过调用creator.GetNameSortedEnumeratorcreator.GetIdSortedEnumerator来实现GetEnumerator。这种结构的行为非常类似于对PersonCollection的引用,只是它们的GetEnumerator方法将绑定到PersonCollection中的不同方法。还可以有一个结构包裹数组的一部分(例如,可以定义一个ArrayRange结构,该结构包含一个名为ArrT[]、一个名为Offset的INT Lengthidx索引属性,该属性对于0到Length-1范围内的索引idx可以访问Arr[idx+Offset]。不幸的是,如果foo是这种结构的只读实例,当前的编译器版本将不允许像foo[3]+=4;这样的操作,因为它们无法确定这些操作是否会尝试写入foo的字段。

    还可以设计一个结构,使其行为类似于一个值类型,该值类型包含一个可变大小的集合(在结构存在时,该集合似乎会被复制),但使其工作的唯一方法是确保结构包含引用的任何对象都不会暴露于可能使其发生变化的任何对象。例如,可以有一个类似数组的结构,该结构包含一个私有数组,并且其索引的"put"方法创建一个新数组,该数组的内容与原始数组的内容类似,但有一个已更改的元素除外。不幸的是,要使这样的结构有效地执行可能有点困难。虽然有时结构语义可以很方便(例如,能够将类似数组的集合传递给例程,调用者和被调用者都知道外部代码不会修改集合,但这可能比要求调用者和被调用者都防御性地复制给定的任何数据要好),但类引用p的要求指向永远不会发生变化的对象通常是一个相当严重的约束。


    不-我不完全同意规则。它们是考虑性能和标准化的好指南,但不考虑可能性。

    正如你在回答中看到的,有很多创造性的方法来使用它们。所以,这些指导方针必须是这样的,总是为了性能和效率。

    在本例中,我使用类以更大的形式表示现实世界中的对象,使用结构表示具有更精确用途的较小对象。正如你所说,"一个更具凝聚力的整体",关键词是"凝聚力"。类将是更多面向对象的元素,而结构可以具有这些特征中的一些,尽管规模较小。国际海事组织

    我在TreeView和ListView标记中经常使用它们,在这些标记中可以非常快速地访问常见的静态属性。我一直在努力以另一种方式获得这些信息。例如,在我的数据库应用程序中,我使用TreeView,其中有表、SP、函数或任何其他对象。我创建并填充我的结构,将其放入标记中,将其拉出,获取所选内容的数据等等。我不会在课堂上这么做的!

    我确实试着保持它们的小,在单实例情况下使用它们,并防止它们发生变化。注意内存、分配和性能是明智的。测试是非常必要的。


    我用BenchmarkDotnet做了一个小基准,以便更好地理解数字中的"结构"好处。我正在测试结构(或类)的数组(或列表)的循环。创建这些数组或列表超出了基准测试的范围——很明显,"类"越重,将占用更多的内存,并且将涉及GC。

    因此,结论是:小心LINQ和隐藏结构装箱/拆箱,并严格使用结构进行微优化。

    另一个通过调用堆栈传递结构/类的基准是https://stackoverflow.com/a/47864451/506147

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    BenchmarkDotNet=v0.10.8, OS=Windows 10 Redstone 2 (10.0.15063)
    Processor=Intel Core i5-2500K CPU 3.30GHz (Sandy Bridge), ProcessorCount=4
    Frequency=3233542 Hz, Resolution=309.2584 ns, Timer=TSC
      [Host] : Clr 4.0.30319.42000, 64bit RyuJIT-v4.7.2101.1
      Clr    : Clr 4.0.30319.42000, 64bit RyuJIT-v4.7.2101.1
      Core   : .NET Core 4.6.25211.01, 64bit RyuJIT


              Method |  Job | Runtime |      Mean |     Error |    StdDev |       Min |       Max |    Median | Rank |  Gen 0 | Allocated |
    ---------------- |----- |-------- |----------:|----------:|----------:|----------:|----------:|----------:|-----:|-------:|----------:|
       TestListClass |  Clr |     Clr |  5.599 us | 0.0408 us | 0.0382 us |  5.561 us |  5.689 us |  5.583 us |    3 |      - |       0 B |
      TestArrayClass |  Clr |     Clr |  2.024 us | 0.0102 us | 0.0096 us |  2.011 us |  2.043 us |  2.022 us |    2 |      - |       0 B |
      TestListStruct |  Clr |     Clr |  8.427 us | 0.1983 us | 0.2204 us |  8.101 us |  9.007 us |  8.374 us |    5 |      - |       0 B |
     TestArrayStruct |  Clr |     Clr |  1.539 us | 0.0295 us | 0.0276 us |  1.502 us |  1.577 us |  1.537 us |    1 |      - |       0 B |
       TestLinqClass |  Clr |     Clr | 13.117 us | 0.1007 us | 0.0892 us | 13.007 us | 13.301 us | 13.089 us |    7 | 0.0153 |      80 B |
      TestLinqStruct |  Clr |     Clr | 28.676 us | 0.1837 us | 0.1534 us | 28.441 us | 28.957 us | 28.660 us |    9 |      - |      96 B |
       TestListClass | Core |    Core |  5.747 us | 0.1147 us | 0.1275 us |  5.567 us |  5.945 us |  5.756 us |    4 |      - |       0 B |
      TestArrayClass | Core |    Core |  2.023 us | 0.0299 us | 0.0279 us |  1.990 us |  2.069 us |  2.013 us |    2 |      - |       0 B |
      TestListStruct | Core |    Core |  8.753 us | 0.1659 us | 0.1910 us |  8.498 us |  9.110 us |  8.670 us |    6 |      - |       0 B |
     TestArrayStruct | Core |    Core |  1.552 us | 0.0307 us | 0.0377 us |  1.496 us |  1.618 us |  1.552 us |    1 |      - |       0 B |
       TestLinqClass | Core |    Core | 14.286 us | 0.2430 us | 0.2273 us | 13.956 us | 14.678 us | 14.313 us |    8 | 0.0153 |      72 B |
      TestLinqStruct | Core |    Core | 30.121 us | 0.5941 us | 0.5835 us | 28.928 us | 30.909 us | 30.153 us |   10 |      - |      88 B |

    代码:

    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
    [RankColumn, MinColumn, MaxColumn, StdDevColumn, MedianColumn]
        [ClrJob, CoreJob]
        [HtmlExporter, MarkdownExporter]
        [MemoryDiagnoser]
        public class BenchmarkRef
        {
            public class C1
            {
                public string Text1;
                public string Text2;
                public string Text3;
            }

            public struct S1
            {
                public string Text1;
                public string Text2;
                public string Text3;
            }

            List<C1> testListClass = new List<C1>();
            List<S1> testListStruct = new List<S1>();
            C1[] testArrayClass;
            S1[] testArrayStruct;
            public BenchmarkRef()
            {
                for(int i=0;i<1000;i++)
                {
                    testListClass.Add(new C1  { Text1= i.ToString(), Text2=null, Text3= i.ToString() });
                    testListStruct.Add(new S1 { Text1 = i.ToString(), Text2 = null, Text3 = i.ToString() });
                }
                testArrayClass = testListClass.ToArray();
                testArrayStruct = testListStruct.ToArray();
            }

            [Benchmark]
            public int TestListClass()
            {
                var x = 0;
                foreach(var i in testListClass)
                {
                    x += i.Text1.Length + i.Text3.Length;
                }
                return x;
            }

            [Benchmark]
            public int TestArrayClass()
            {
                var x = 0;
                foreach (var i in testArrayClass)
                {
                    x += i.Text1.Length + i.Text3.Length;
                }
                return x;
            }

            [Benchmark]
            public int TestListStruct()
            {
                var x = 0;
                foreach (var i in testListStruct)
                {
                    x += i.Text1.Length + i.Text3.Length;
                }
                return x;
            }

            [Benchmark]
            public int TestArrayStruct()
            {
                var x = 0;
                foreach (var i in testArrayStruct)
                {
                    x += i.Text1.Length + i.Text3.Length;
                }
                return x;
            }

            [Benchmark]
            public int TestLinqClass()
            {
                var x = testListClass.Select(i=> i.Text1.Length + i.Text3.Length).Sum();
                return x;
            }

            [Benchmark]
            public int TestLinqStruct()
            {
                var x = testListStruct.Select(i => i.Text1.Length + i.Text3.Length).Sum();
                return x;
            }
        }


    我的规则是

    1、始终使用课堂;

    2.如果有任何性能问题,我会根据@iabstract提到的规则,尝试将一些类更改为struct,然后进行测试,看看这些更改是否可以提高性能。


    类是引用类型。当创建类的对象时,对象所分配到的变量只保存对该内存的引用。当对象引用分配给新变量时,新变量引用原始对象。通过一个变量所做的更改反映在另一个变量中,因为它们都引用相同的数据。结构是值类型。创建结构时,将结构分配给的变量将保存结构的实际数据。将结构赋给新变量时,将复制该结构。因此,新变量和原始变量包含同一数据的两个单独副本。对一个副本所做的更改不会影响另一个副本。通常,类用于建模更复杂的行为,或者在创建类对象后要修改的数据。结构最适合于主要包含在创建结构后不打算修改的数据的小型数据结构。

    类和结构(C编程指南)


    我只是处理Windows通信基金会[WCF]命名管道,我确实注意到使用结构是有意义的,以确保数据交换是值类型而不是引用类型。


    我认为一个好的第一个近似值是"从不"。

    我认为一个好的二次近似值是"从不"。

    如果你非常渴望表现,考虑一下,但要经常衡量。


    C结构是类的轻量级替代。它可以和类做几乎相同的工作,但是使用结构而不是类的成本更低。这样做的原因有点技术性,但总而言之,类的新实例放在堆上,新实例化的结构放在堆上。此外,您不会像处理类那样处理对结构的引用,而是直接处理结构实例。这也意味着当您将结构传递给函数时,它是按值传递的,而不是作为引用传递的。有关函数参数的更多信息,请参见本章。

    因此,当您希望表示更简单的数据结构时,尤其是当您知道将要实例化大量数据结构时,应该使用结构。在.NET框架中有许多示例,其中Microsoft使用了结构而不是类,例如点结构、矩形结构和颜色结构。


    结构可用于提高垃圾收集性能。虽然您通常不必担心GC性能,但在某些情况下,它可能是一个杀手。类似于低延迟应用程序中的大缓存。请参阅本文中的示例:

    http://00sharp.wordpress.com/2013/07/03/a-case-for-the-structure/


    结构或值类型可用于以下场景-

  • 如果要阻止垃圾收集对象。
  • 如果它是简单类型,并且没有成员函数修改其实例字段
  • 如果不需要从其他类型派生或派生到其他类型。
  • 您可以在此链接上了解有关值类型和值类型的更多信息


    简单地说,如果:

    1-不需要更改对象属性/字段。我的意思是你只想给他们一个初始值,然后读出来。

    2-对象中的属性和字段是值类型,它们不太大。

    如果是这种情况,您可以利用结构来获得更好的性能和优化的内存分配,因为它们只使用堆栈,而不同时使用堆栈和堆(在类中)。


    我很少对事物使用结构。但那只是我。这取决于我是否需要对象可以为空。

    如其他答案所述,我将类用于现实世界中的对象。我也有这样的想法:结构用于存储少量数据。


    神话1:结构是轻量级类好的。

    这个神话有多种形式。有些人认为价值类型不能或不应该有方法或其他重要的行为,它们应该简单使用数据传输类型,只有公共字段或简单属性。日期时间类型是很好的反例:就存在而言,它是一种价值类型是有意义的。一个基本单位,如一个数字或一个字符,它也有意义能够根据其值进行计算。从另一个角度看问题不管怎样,数据传输类型应该经常是引用类型应该基于所需的值或引用类型语义,而不是简单的类型。其他人认为价值类型比参考类型"轻"性能。事实上,在某些情况下,值类型的性能更高-它们不需要垃圾收集,除非它们是装箱的,没有类型例如,标识开销,不需要取消引用。但在其他方法是,引用类型更具性能的参数传递,将值赋给变量、返回值和类似的操作只需要4或8个字节(取决于运行的是32位还是64位CLR),而不是复制所有数据。想象一下,如果arraylist是某种"纯"值类型,并且将arraylist表达式传递给方法涉及复制其所有数据!几乎不管怎样,这种决定并不能真正决定性能。瓶颈几乎永远不会出现在您认为它们会出现的地方,在您根据性能做出设计决策之前,您应该衡量不同的选项。值得注意的是,这两种信仰的结合也不起作用。它不管一个类型有多少个方法(无论是类还是结构)——每个实例所占用的内存不受影响。(记忆方面有成本对代码本身负责,但这只发生一次,而不是针对每个实例。)好的。

    神话2:引用类型位于堆上;值类型位于堆栈上好的。

    这一点通常是由重复它的人的懒惰造成的。第一部分正确。始终在堆上创建引用类型的实例。这是第二部分是引起问题的原因。正如我已经注意到的,变量的值在声明它的任何地方都存在,所以如果您有一个实例变量为int类型的类,那么该变量对于任何给定对象的值将始终位于该对象的其余数据所在的位置。在堆中。仅局部变量(方法中声明的变量)和方法参数位于堆栈上。在C 2和更高版本中,即使某些局部变量也不能活在堆栈上,正如我们在第5章中看到的匿名方法。这些概念现在是否相关?如果您正在编写托管代码,那么您应该让运行时担心内存的最佳使用方式。事实上,语言规范并不能保证什么是生命其中,未来的运行时可以在堆栈上创建一些对象,如果知道它可以摆脱它,或者C编译器可以生成几乎不使用堆栈。下一个神话通常只是一个术语问题。好的。

    神话3:默认情况下,对象在C中通过引用传递好的。

    这可能是传播最广的神话。再说一次,制造这个的人索赔通常(尽管并非总是)知道C的实际行为,但他们不知道"引用传递"的真正含义是什么?不幸的是,对于那些知道它是什么意思。传递引用的形式定义比较复杂,涉及L值和类似的计算机科学术语,但重要的是如果你通过变量通过引用,您调用的方法可以通过更改调用方变量的参数值来更改其值。现在,记住引用的值类型变量是引用,而不是对象本身。您可以更改一个参数引用的对象,但不通过引用传递参数本身。例如,以下方法更改了StringBuilder的内容对象,但调用方的表达式仍将引用与之前:好的。

    1
    2
    3
    4
    void AppendHello(StringBuilder builder)
    {
        builder.Append("hello");
    }

    调用此方法时,参数值(对StringBuilder的引用)为通过值。如果要更改builder变量的值,例如,当语句生成器为空时,该更改不会被来电者看到,与神话相反。有趣的是,这不仅是神话中的"参照"点不准确,而且"物体被传递"点也不准确。对象本身也不会被传递通过引用或值。当涉及引用类型时,变量为通过引用传递或通过值传递参数(引用)的值。除了任何其他问题,这就回答了当null为如果传递对象,则用作按值参数,这将导致问题,因为没有一个对象可以通过!相反,空引用通过值的方式与任何其他引用的方式相同。如果这个快速的解释让您感到困惑,您可能会想看看我的文章,"参数传入C",(http://mng.bz/otvt),它将涉及更多细节。这些神话不是唯一的。拳击和拆箱有相当一部分误解,下一步我会努力消除。好的。

    参考文献:乔恩·斯基特的《深度》第三版好的。好啊。


    结构在大多数方面都像类/对象。结构可以包含函数、成员,并且可以继承。但是结构在C中只用于数据保存。结构确实比类占用更少的RAM,而且垃圾收集器更容易收集。但是当您在您的结构中使用函数时,编译器实际上将这个结构作为类/对象非常相似,所以如果您需要一些函数,那么使用类/对象。