首页 文章

何时使用struct?

提问于
浏览
1247

什么时候应该在C#中使用struct而不是class?我的概念模型是当项只是值类型的集合时使用结构 . 一种逻辑上将它们组合在一起形成一个有凝聚力的整体的方法 .

我遇到了这些规则here

  • 结构应该表示单个值 .

  • 结构的内存占用量应小于16个字节 .

  • 创建后不应更改结构 .

这些规则有用吗?结构在语义上意味着什么?

28 回答

  • 138

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

    Classes and Structs (C# Programming Guide)

  • 25

    我不同意原帖中的规定 . 这是我的规则:

    1)存储在数组中时使用结构体来提高性能 . (另见When are structs the answer?

    2)您需要在代码中将结构化数据传递到C / C或从C / C传递结构化数据

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

    • 它们与赋值下的"normal objects"(引用类型)和作为参数传递时的行为不同,这可能导致意外行为;如果查看代码的人不知道他们正在处理结构,那么这尤其危险 .

    • 他们不能被继承 .

    • 将结构作为参数传递比类更昂贵 .

  • 8

    除了运行时和其他各种用于PInvoke目的的值类型之外,您应该只在两种情况下使用值类型 .

    • 何时需要复制语义 .

    • 当您需要自动初始化时,通常在这些类型的数组中 .

  • 38

    我用BenchmarkDotNet做了一个小基准,以便更好地理解"struct"数字的好处 . 我_168821的范围 - 很明显,"class"更重将使用更多内存,并将涉及GC .

    所以结论是:小心LINQ和隐藏的结构装箱/拆箱,并使用结构进行微优化,严格保留数组 .

    附:关于通过调用堆栈传递struct / class的另一个基准是https://stackoverflow.com/a/47864451/506147

    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 |
    

    码:

    [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;
            }
        }
    
  • 15

    这是一条基本规则 .

    • 如果所有成员字段都是值类型,则创建 struct .

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

    Exmaples

    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
    }
    
  • 2

    .NET支持 value typesreference types (在Java中,您只能定义引用类型) . reference types 的实例在托管堆中分配,并且在没有对它们的未完成引用时进行垃圾回收 . 另一方面, value types 的实例在 stack 中分配,因此一旦其范围结束,就会回收分配的内存 . 当然, value types 按值传递, reference types 按引用传递 . 除System.String外,所有C#原始数据类型都是值类型 .

    When to use struct over class,

    在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 ,当您需要复制语义或需要自动初始化时,通常在这些类型的_168785中 .

  • 33

    我的规则是

    1,始终使用课程;

    2,如果有任何性能问题,我会尝试根据@IAbstract提到的规则将某些类更改为struct,然后进行测试以查看这些是否存在变化可以提高绩效 .

  • 5

    struct 是值类型 . 如果将结构分配给新变量,则新变量将包含原始副本 .

    public struct IntStruct {
        public int Value {get; set;}
    }
    

    以下结果导致存储在内存中的结构的 5 instances

    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.
    

    class 是引用类型 . 将类分配给新变量时,该变量包含对原始类对象的引用 .

    public class IntClass {
        public int Value {get; set;}
    }
    

    以下结果导致内存中类对象的 only one instance .

    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
    

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

    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
    
  • 561

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

    • 如果要防止垃圾回收收集对象 .

    • 如果它是一个简单类型,并且没有成员函数修改其实例字段

    • 如果不需要从其他类型派生或派生到其他类型 .

    您可以了解有关值类型和值的更多信息types here on this link

  • 16

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

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

  • 5

    在需要使用StructLayoutAttribute显式指定内存布局的情况下,需要使用"struct" - 通常用于PInvoke .

    编辑:注释指出您可以使用StructLayoutAttribute的类或结构,这当然是正确的 . 在实践中,您通常会使用一个结构 - 它在堆栈和堆上分配,如果您只是将参数传递给非托管方法调用,这是有意义的 .

  • 56

    结构适用于数据的原子表示,其中所述数据可以由代码多次复制 . 克隆一个对象通常比复制一个结构更昂贵,因为它涉及分配内存,运行构造函数和完成它时解除分配/垃圾回收 .

  • 148

    简而言之,使用struct if:

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

    对象中的2-属性和字段是值类型,并且它们不是那么大 .

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

  • 9

    C#或其他.net语言中的结构类型通常用于保存应该像固定大小的值组一样的事物 . 结构类型的一个有用方面是结构类型实例的字段可以通过修改其所在的存储位置来修改,而不是以其他方式 . 有可能以这样的方式对结构进行编码:改变任何字段的唯一方法是构造一个全新的实例,然后使用结构赋值通过用新实例中的值覆盖它们来改变目标的所有字段,但是除非struct没有提供创建其字段具有非默认值的实例的方法,否则如果struct本身存储在可变位置,则其所有字段都是可变的 .

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

    也可以设计一个结构来表现类似于一个值类型,它保存一个可变大小的集合(每当结构都会被复制时),但唯一的方法就是确保没有对象 . struct持有一个引用将暴露于任何可能改变它的东西 . 例如,可以有一个类似于数组的结构,它包含一个私有数组,其索引的“put”方法创建一个新数组,其内容类似于原始数组,但一个更改的元素除外 . 不幸的是,使这种结构表现有些困难有效率的 . 虽然有时候结构语义可以很方便(例如,能够将类似数组的集合传递给例程,调用者和被调用者都知道外部代码不会修改集合,可能比要求调用者和调用者更好 . callee防御性地复制他们给出的任何数据,类引用指向永远不会变异的对象的要求通常是非常严格的约束 .

  • 8

    在我看来,struct没有强大的语义,可以让用户深入了解何时使用它 .

    它类似于一个类,但湖的大部分功能 . 它是一种降级版本的类 . 有很多关于什么时候不使用它的说法,但很少说何时使用它 .

    IMO,没有理由为什么结构应该首先用OO语言实现 . 实际上原始类型不应该存在于纯粹的OO语言中,但我离题了 .

    它可能是一种优化内容的方法 . 一种拳击免费的东西,可以在一些呼叫网站上进行优化 .

    我的2分,我会说它违反了语言原则的KISS并且尽可能地避免它 .

  • -8

    C#struct是类的轻量级替代品 . 它可以与类几乎相同,但使用结构而不是类不那么“昂贵” . 这样做的原因有点技术性,但总而言之,类的新实例放在堆上,其中新实例化的结构放置在堆栈上 . 此外,您不处理对结构的引用,例如类,而是直接使用结构实例 . 这也意味着当您将结构传递给函数时,它是按值而不是作为引用 . 关于功能参数的章节中有更多相关内容 .

    因此,当您希望表示更简单的数据结构时,您应该使用结构,特别是如果您知道将要实例化大量数据结构 . .NET框架中有很多示例,其中Microsoft使用了结构而不是类,例如Point,Rectangle和Color结构 .

  • 10

    我刚刚处理Windows Communication Foundation [WCF]命名管道,我确实注意到使用Structs以确保数据交换是 value type 而不是 reference type 确实有意义 .

  • 3

    不 - 我不完全同意这些规则 . 它们是考虑性能和标准化的良好指导方针,但不是考虑到可能性 .

    正如您在回复中所看到的,有很多创造性的方法可以使用它们 . 因此,这些指南只需要这样,总是为了性能和效率 .

    在这种情况下,我使用类来表示更大形式的真实世界对象,我使用结构来表示具有更精确用途的较小对象 . 你说的方式,“一个更有凝聚力的整体 . ”关键词具有凝聚力 . 这些类将是更多面向对象的元素,而结构体可以具有一些这些特征,尽管规模较小 . IMO .

    我在Treeview和Listview标签中经常使用它们,可以非常快速地访问常见的静态属性 . 我一直在努力以另一种方式获得这些信息 . 例如,在我的数据库应用程序中,我使用Treeview,其中包含表,SP,函数或任何其他对象 . 我创建并填充我的结构,将其放在标签中,将其拉出,获取选择的数据等等 . 我不会上课!

    我确实尝试将它们保持在小的状态,在单实例情况下使用它们,并防止它们发生变化 . 了解内存,分配和性能是明智的 . 测试是如此必要 .

  • 17

    我认为良好的初步近似是“从不” .

    我认为好的第二个近似值是“永远不会” .

    如果你非常渴望性能,请考虑它们,但随后要测量 .

  • -10

    除了"it is a value"答案之外,使用结构的一个特定方案是当你有一组导致垃圾收集问题的数据时,你有很多对象 . 例如,Person实例的大型列表/数组 . 这里的自然隐喻是一个类,但是如果你有大量长寿的Person实例,它们最终会堵塞GEN-2并导致GC停顿 . 如果场景保证,这里的一种可能的方法是使用Person structs 的数组(不是列表),即 Person[] . 现在,不是在GEN-2中拥有数百万个对象,而是在LOH上有一个块(我假设这里没有字符串等 - 即没有任何引用的纯值) . 这对GC影响很小 .

    使用这些数据很尴尬,因为数据可能超出了结构的大小,并且您不希望一直复制胖值 . 但是,直接在数组中访问它不会复制结构 - 它就位(与列表索引器相比,它会复制) . 这意味着很多工作与索引:

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

    请注意,保持值本身不可变将有助于此处 . 对于更复杂的逻辑,请使用带有by-ref的方法参数:

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

    同样,这是就地 - 我们没有复制 Value .

    在非常具体的情况下,这种策略可以非常成功;但是,这是一个相当先进的scernario,只有当你知道自己在做什么以及为什么这样做时才应该尝试 . 这里的默认值是一个类 .

  • 3

    来自C# Language specification

    1.7结构类与类一样,结构是可以包含数据成员和函数成员的数据结构,但与类不同,结构是值类型,不需要堆分配 . 结构类型的变量直接存储结构的数据,而类类型的变量存储对动态分配的对象的引用 . 结构类型不支持用户指定的继承,并且所有结构类型都隐式继承自类型对象 . 结构对于具有值语义的小型数据结构特别有用 . 复数,坐标系中的点或字典中的键值对都是结构的好例子 . 对小型数据结构使用结构而不是类可以使应用程序执行的内存分配数量产生很大差异 . 例如,以下程序创建并初始化100个点的数组 . 将Point实现为类,实例化101个单独的对象 - 一个用于数组,一个用于100个元素 .

    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);
       }
    }
    

    另一种方法是使Point成为一个结构 .

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

    现在,只实例化一个对象 - 数组的对象 - 并且Point实例以串联方式存储在数组中 .

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

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

    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的影响 .

    前面的例子强调了结构的两个局限性 . 首先,复制整个结构通常比复制对象引用效率低,因此对于结构而言,赋值和值参数传递可能比使用引用类型更昂贵 . 其次,除了ref和out参数之外,不可能创建对结构的引用,结构排除了它们在许多情况下的使用 .

  • 9

    Struct可用于提高垃圾收集性能 . 虽然您通常不必担心GC性能,但有些情况下它可能是一个杀手 . 就像低延迟应用程序中的大型缓存一样 . 请参阅此帖子以获取示例:

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

  • 18

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

    在此上下文中,您列出的三条准则对我没有用 . 当我需要在特定顺序中写出四百个字节的东西时,我将定义一个四百字节的结构,并且我将填充它应该具有的任何不相关的值,并且我将要去设置当时最有意义的方式 . (好吧,四百个字节会很奇怪 - 但是当我以Excel文件为生,我正在处理全部最多大约四十个字节的结构,因为那是BIFF记录的大小 . )

  • 9

    OP引用的源代码具有一定的可信度......但是微软呢 - 结构使用的立场是什么?我找了一些额外的learning from Microsoft,这是我发现的:

    如果类型的实例很小并且通常是短暂的或者通常嵌入在其他对象中,则考虑定义结构而不是类 . 除非类型具有以下所有特征,否则不要定义结构:它在逻辑上表示单个值,类似于基本类型(整数,双精度等) . 它的实例大小小于16个字节 . 这是不可改变的 . 它不必经常装箱 .

    Microsoft始终违反这些规则

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

    [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的实现,Dictionary使用 Enumerator 结构,每次请求枚举器时它都会复制...这是有意义的 .

    • Dictionary类的内部 . Enumerator 是公共的,因为Dictionary是可枚举的,并且必须具有与IEnumerator接口实现相同的可访问性 - 例如IEnumerator getter .

    Update - 此外,要意识到当一个struct实现一个接口时 - 就像Enumerator那样 - 并且被强制转换为该实现类型,该struct将成为一个引用类型并被移动到堆中 . 在Dictionary类的内部,Enumerator仍然是一个值类型 . 但是,只要方法调用 GetEnumerator() ,就会返回引用类型 IEnumerator .

    我们在这里没有看到的任何尝试或证明要求保持结构不可变或维持实例大小只有16个字节或更少:

    • 上面的结构体中没有任何内容声明 readonly - not 不可变

    • 这些结构的大小可能超过16个字节

    • Entry 具有未确定的生命周期(从 Add() ,到 Remove()Clear() 或垃圾回收);

    并且... 4.两个结构存储TKey和TValue,我们都知道它们很有能力作为参考类型(添加奖励信息)

    尽管有散列键,但字典很快部分是因为实例化结构比引用类型更快 . 在这里,我有一个 Dictionary<int, int> 存储300,000个随机整数和顺序递增的键 .

    容量:312874 MemSize:2660827字节已完成大小调整:5ms填写时间:889ms

    Capacity :必须调整内部数组大小之前可用的元素数 .

    MemSize :通过将字典序列化为MemoryStream并获得字节长度(对于我们的目的来说足够准确)来确定 .

    Completed Resize :将内部数组的大小从150862个元素调整为312874个元素所需的时间 . 当你想通过 Array.CopyTo() 顺序复制每个元素时,这不是太破旧了 .

    Total time to fill :由于记录和我添加到源中的 OnResize 事件而确实存在偏差;然而,在操作期间调整15次时,仍然令人印象深刻地填充300k整数 . 出于好奇,如果我已经知道容量,那么总的填充时间是多少? 13ms

    那么,现在,如果 Entry 是一个类呢?这些时间或指标真的会有那么大差异吗?

    容量:312874 MemSize:2660827字节已完成大小调整:26ms总填充时间:964ms

    显然,最大的区别在于调整大小 . 如果使用容量初始化Dictionary,会有什么不同吗?不足以关心... 12ms .

    会发生什么,因为 Entry 是一个结构,它不需要像引用类型那样进行初始化 . 这既是 Value 类型的美丽又是祸根 . 为了使用 Entry 作为引用类型,我必须插入以下代码:

    /*
     *  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找到 . 简而言之:

    不要为结构提供默认构造函数 . 如果结构定义了默认构造函数,则在创建结构的数组时,公共语言运行库会自动在每个数组元素上执行默认构造函数 . 某些编译器(如C#编译器)不允许结构具有默认构造函数 .

    它实际上非常简单,我们将从Asimov's Three Laws of Robotics借用:

    • 结构必须安全使用

    • 结构必须有效地执行其功能,除非这会违反规则#1

    • 结构在使用过程中必须保持完整,除非要求销毁以满足规则#1

    ......我们从中得到什么:简而言之,要对 Value 类型的使用负责 . 它们快速有效,但如果维护不当(即无意复制),则有能力引发许多意外行为 .

  • 4

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

    编辑

    不确定为什么人们正在贬低这一点,但这是一个有效的观点,并且在操作澄清他的问题之前做出,这是结构的最基本的基本原因 .

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

  • 83

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

    如其他答案中所述,我使用类来实现真实世界的对象 . 我也有结构的心态用于存储少量数据 .

  • 3

    结构在很多方面类似于类/对象 . 结构可以包含函数,成员并且可以继承 . 但是C#中的结构仅用于 data holding . 结构比类更 take less RAM ,是 easier for garbage collector to collect . 但是当你在结构中使用函数时,然后编译器实际上采用与类/对象非常相似的结构,所以如果你想要 functions, then use class/object 的东西 .

  • 14

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

相关问题