关于C#:什么时候结构比类更好?

When structures are better than classes?

本问题已经有最佳答案,请猛点这里访问。

的副本:何时在C中使用结构?

在Microsoft.NET 2.0/3.5中使用结构而不是某些类有实际的原因吗?

"结构和类之间有什么区别?"-这可能是有关".net developer"空缺的intrview最流行的问题。面试官认为唯一正确的答案是"结构是在堆栈上分配的,类是在堆栈上分配的",并且没有进一步的问题。

一些谷歌搜索显示:

a) structures have numerous limitations and no additional abilities in comparison to classes and
b) stack (and as such
structures) can be faster on very specialized conditions including:

  • size of data chunk less that 16 bytes
  • no extensive boxing/unboxing
  • structure's members are nearly immutable
  • whole set of data is not big (otherwise we get stack overflow)

(如果错误或未满,请更正/添加到此列表)

据我所知,大多数典型的商业项目(erm、accounting、银行解决方案等)甚至没有定义一个结构,而是将所有自定义数据类型定义为类。在这种方法中,是否存在错误或至少不完美的地方?

注意:问题是关于Mill Business Apps的运行,请不要列出"异常"情况,如游戏开发、实时动画、向后兼容性(com/interop)、非托管代码等-这些答案已经在这个类似的问题下:

何时使用结构?


As far as I know, most typical commercial projects (ERM, accouting, solutions for banks, etc.) do not define even a single structure, all custom data types are defined as classes instead. Is there something wrong or at least imperfect in this approach?

不!一切都很好。您的一般规则应该是在默认情况下始终使用对象。毕竟,我们谈论的是面向对象的编程是有原因的,而不是面向结构的编程(结构本身缺少一些OO原则,比如继承和抽象)。

但是,如果出现以下情况,结构有时会更好:

  • 您需要精确控制使用的内存量(结构使用的内存(取决于大小),这比对象的内存要少得多。
  • 您需要精确控制内存布局。这对于与Win32或其他本机API进行互操作特别重要。
  • 你需要尽可能快的速度。(在许多具有较大数据集的场景中,当正确使用结构时,可以获得相当快的速度)。
  • 您需要减少内存浪费,并在数组中拥有大量结构化数据。特别是与数组结合使用,您可以通过结构节省大量内存。
  • 您正在广泛地使用指针。然后结构提供了许多有趣的特性。


IMO最重要的用例是小的复合实体的大数组。想象一个包含10^6复数的数组。或包含1000x1000 24位RGB值的二维数组。在这种情况下,使用结构而不是类可以产生巨大的差异。

编辑:澄清:假设你有一个结构

1
2
3
4
struct RGB
{
   public byte R,G,B;
}

如果声明一个1000x1000 rgb值的数组,则该数组将占用正好3 MB的内存,因为值类型是以内联方式存储的。

如果使用类而不是结构,则数组将包含1000000个引用。仅此一项就需要4或8 MB(在64位机器上)的内存。如果用单独的对象初始化所有项,这样就可以单独修改这些值,那么就有1000000个对象在托管堆上旋转以保持GC繁忙。每个对象都有2个引用的开销(IIRC),即对象将使用11/19MB的内存。总的来说,这是简单结构版本的5倍内存。


堆栈分配值类型的一个优点是它们是线程的本地值。这意味着它们本质上是线程安全的。对于堆中的对象不能这样说。

当然,这假设我们谈论的是安全的托管代码。


与类的另一个区别是,当您将一个结构实例分配给一个变量时,您不仅要复制一个引用,而且确实要复制整个结构。因此,如果修改其中一个实例(无论如何都不应该这样做,因为结构实例是不可变的),那么另一个实例就不会被修改。


如果类型的目的是将独立值的小固定集合与管道胶带(例如点的坐标、枚举字典项的键和相关值、六项二维转换矩阵等)绑定在一起,那么从效率和语义的角度来看,最好的表示形式可能是可变的。Le露天场地结构。请注意,这代表了一种非常不同的使用场景,在这种情况下,结构表示一个单一的统一概念(例如,DecimalDateTime,而微软关于何时使用结构的建议给出了仅适用于后者的建议。Microsoft描述的"不变"结构的样式只适用于表示一个单一的统一概念;如果需要表示一个固定的独立值的小集合,则正确的选择不是不变的类(它提供的性能较差),也不是可变的类(在许多情况下,它提供的语义都不正确)。而不是一个公开的字段结构(如果使用得当,它提供了更好的语义和性能)。例如,如果有一个包含二维变换矩阵的结构MyTransform,则使用如下方法:

1
2
3
4
5
static void Offset(ref it, double x, double y)
{
  it.dx += x;
  it.dy += y;
}

1
2
3
4
static void Offset(ref it, double x, double y)
{
  it = new Transform2d(it.xx, int.xy, it.yx, it.yy, it.dx+x, it.dy+y);
}

1
2
3
4
Transform2d Offset(double dx, double dy)
{
  it = new Transform2d(xx, xy, yx, yy, dx+x, dy+y);
}

知道dx和dy是Transform2d的字段,就足以知道第一种方法修改了这些字段,没有其他副作用。相反,为了知道其他方法的作用,我们必须检查构造函数的代码。


到目前为止,所有的好答案都是这样的……我只需要补充一下,根据定义,值类型是不可以为空的,因此是一个很好的候选者,可以在不想为创建类的新实例和将其分配给字段而烦恼的情况下使用,例如……

1
2
3
4
5
6
7
8
9
10
struct Aggregate1
{
    int A;
}

struct Aggregate2
{
    Aggregate1 A;
    Aggregate1 B;
}

注意:如果Aggregate1是一个类,那么您必须手动初始化aggregate2中的字段…

1
2
3
Aggregate2 ag2 = new Aggregate2();
ag2.A = new Aggregate1();
ag2.B = new Aggregate1();

很显然,只要aggregate1是一个结构,就不需要这样做……当您创建一个类/结构继承性,以便使用XmlSerializer进行序列化/反序列化时,这可能会很有用。在这种情况下,通过使用结构,许多看似神秘的异常将消失。


有一些很好的答案涉及到使用结构与类之间的实用性,反之亦然,但我认为您对结构不变的最初评论是一个很好的论据,可以解释为什么在LOB应用程序的高级设计中更经常使用类。

在域驱动设计http://www.infoq.com/minibooks/domain-driven-design-quickly中,实体/类和值对象/结构之间存在某种程度的平行性。DDD中的实体是业务域中的项目,我们需要使用标识符(如customerid、productid等)跟踪其标识。值对象是我们可能感兴趣的值,但我们不使用标识符(如price或orderdate)跟踪其标识的项目。实体在DDD中是可变的,除了其标识字段,而值对象没有标识。

因此,在对典型的业务实体建模时,通常会设计一个类和一个标识属性,该属性跟踪业务对象从持久性存储来回的标识。尽管在运行时,我们可能会更改业务对象实例上的所有属性值,但只要标识符是不可变的,实体的标识就会保留。对于与金钱或时间相对应的业务概念,结构是一种自然的适合,因为即使在执行计算时创建了一个新实例,也没关系,因为我们不跟踪一个标识,只存储一个值。


有时,您只想在组件之间传输数据,那么struct比class更好。例如,只携带数据的数据传输对象(DTO)。