【问题标题】:Struct vs Class for long lived objects长寿命对象的结构与类
【发布时间】:2009-03-03 21:57:55
【问题描述】:

当您需要非常小的对象时,比如说包含 2 个浮点属性,并且您将拥有数百万不会立即“销毁”的对象,是结构体还是类更好的选择?

就像在 xna 中作为库一样,有 point3s 等作为结构体,但如果您需要长时间保留这些值,会不会对性能造成威胁?

【问题讨论】:

    标签: c# .net class struct


    【解决方案1】:

    与大多数关于结构的问题相反,这实际上似乎是结构的一个很好的用途。如果它包含的数据是值类型,并且您将使用其中的很多,那么结构会很好地工作。

    一些提示:

    :: 结构体不应大于 16 字节,否则将失去性能优势。

    :: 使结构不可变。这使得用法更清晰。

    例子:

    public struct Point3D {
    
       public float X { get; private set; }
       public float Y { get; private set; }
       public float Z { get; private set; }
    
       public Point3D(float x, float y, float z) {
          X = x;
          Y = y;
          Z = z;
       }
    
       public Point3D Invert() {
          return new Point3D(-X, -Y, -Z);
       }
    
    }
    

    【讨论】:

    • 这可能是 readonly 关键字的一个很好的用途,也就是说,您可以使用只读字段,而不是将 X、Y 和 Z 声明为属性。这样做的好处是你不会错误地破坏你的不变性。
    • myRect = new Rectangle(myRect.X, myRect.Y+4, myRect.Width, myRect.Height); 真的比myRect.Y += 4; 更清晰吗?改变结构的方法是有问题的,但在许多情况下,暴露的字段应该是常态。
    • @supercat:如果您需要更改结构的一部分,那么它不会像值类型那样代表单个实体。此外,仅当您将值作为单个变量时,公开字段才方便,如果它是属性或数组,那么您不能那样更改它们。您可以创建返回新值的方法来处理这样的操作:myRect = myRect.AddY(4);
    • @Guffa:结构是存储相关但正交值(例如点的坐标)集合的最佳格式;虽然 .net 遗憾地没有提供任何特别好的模式来修改集合中的结构,但我建议 var tempRect = ListOfRects[index]; tempRect.Y += 4; ListOfRects[index] = tempRect; 仍然比 var tempRect = ListOfRects[index]; ListOfRects[index] = new Rectangle(tempRect.X, tempRect.Y+4, tempRect.Width, tempRect.Height); 更清晰。除其他外,一眼就能看出它正在修改哪些字段......
    • ...无需检查构造函数的代码以确保它不会(意外或有意地)执行诸如转置 X 和 Y 坐标之类的操作。将结构限制为只读属性的建议可以追溯到早期的 C# 编译器,其中ListOfRects[index].Y += 4 会生成虚假代码而不是产生编译时错误。 PODS(普通旧数据结构)具有与类类型对象不同的语义,但它们都具有相同的语义。假设有一个List<CRect>,其中CRect 是一个与Rectangle 具有相同属性的可变类。怎么会...
    【解决方案2】:

    答案取决于最终存储对象/值的位置。如果要将它们存储在像 ArrayList 这样的无类型集合中,那么您最终将它们装箱。装箱为结构创建对象包装器,其占用空间与类对象相同。另一方面,如果您使用 T[] 或 List 之类的类型化数组,那么使用结构将仅存储每个元素的实际数据,并且仅存储整个集合而不是其元素的足迹。

    因此,在 T[] 数组中使用结构更有效。

    【讨论】:

      【解决方案3】:

      最大的问题是内存是分配在栈上还是堆上。默认情况下,结构体进入堆栈,并且堆栈通常在空间方面受到更多限制。所以像这样创建一大堆结构可能是个问题。

      不过,在实践中,我真的认为这没什么大不了的。如果你有那么多它们,它们很可能是某个类实例(在堆上)的一部分。

      【讨论】:

      • 栈空间不是结构的问题,因为你很少有这么多的局部变量。如果您创建一个结构数组,它将分配在堆上,而不是堆栈上。
      • 我认为这就是我的观点:如果你确实拥有那么多,那是个问题,但你不可能拥有那么多。
      • 如果你有那么多变量,不管它是一个结构还是一个类,这都是一个问题,因为引用也会被分配到堆栈上...... :)
      • 谢谢乔尔。那么如果有一个 1M point3 结构值的列表,它们会存储在堆上吗?
      【解决方案4】:

      Struct 似乎适合这个应用程序。

      请记住,“需要保留这些值”意味着它们存储在堆中某处,可能是类实例的数组字段。

      需要注意的一点是,这会导致在大对象堆上进行分配。目前尚不清楚这个堆如何进行碎片整理(如果有的话),但是对于寿命很长的对象来说,这可能不是问题。

      对数以百万计的这些数据类型使用类可能会因为取消引用的剪切量而代价高昂,而对这种类型的操作可能会发生这种情况。

      【讨论】:

        【解决方案5】:

        通常,相同类型的非别名(即非共享)数据的大型数组最好存储在结构中以提高性能,因为您减少了间接次数。 (另见when-are-structs-the-answer)。 class 和 struct 之间的确切性能差异取决于您的使用情况。 (例如,在操作中,你只访问结构的一部分吗?你做很多临时复制吗?如果结构很小,它可能总是更好用,但如果它很大,创建临时副本可能会减慢你的速度。如果你使其不可变,您将必须始终复制整个内容以更改值。)

        如有疑问,请测量。

        由于您对此类测量可能不明显的可能的长期影响感兴趣,因此请注意此类数组可能存储在大对象堆中,应该重复使用而不是销毁和重新分配. (见CRL Inside Out: Large Object Heap Uncovered。)

        在调用中传递较大尺寸的结构时,您可能希望使用 ref 参数传递它们以避免复制。

        【讨论】:

          【解决方案6】:

          值类型(结构)适用于不经常在堆上分配的类型,即它们大多包含在另一个引用或值类型中。

          您给出的 Vector3 示例是一个完美的示例。您很少会在堆中悬挂 Vector3,它们大部分时间会包含在本身在堆中的类型中,或者用作局部变量,在这种情况下,它将分配在堆栈上。

          【讨论】:

          • 谢谢,所以结构只有在用作局部变量时才会在堆上,没有别的?
          • 不,恰恰相反。像结构这样的值类型只有在它是局部变量时才会在堆栈上分配。如果它是一个类的成员,它会作为对象在堆上的内存区域的一部分进行分配。
          猜你喜欢
          • 2011-03-21
          • 1970-01-01
          • 1970-01-01
          • 2016-01-12
          • 2017-07-21
          • 2023-04-10
          • 2013-10-14
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多