什么时候应该在C#中使用结构而不是类?我的概念模型是,当项只是值类型的集合时,使用结构。一种将它们逻辑地结合在一起的方法。

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

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

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


当前回答

在需要使用StructLayoutAttribute显式指定内存布局(通常用于PInvoke)的情况下,需要使用“结构”。

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

其他回答

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

处理这些数据是很困难的,因为数据对于一个结构来说可能太大了,而且您不想一直复制胖值。然而,直接在数组中访问它不会复制结构-它是在适当的位置(与列表索引器不同,它会复制)。这意味着大量的索引工作:

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

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

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

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

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

我刚刚在处理Windows Communication Foundation[WCF]命名管道,我注意到使用Structs确实有意义,以确保数据交换是值类型而不是引用类型。

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

当存储在数组中时,可以使用结构来提高性能。(另请参见结构何时是答案?)在向C/C传递结构化数据的代码中需要它们++除非需要,否则不要使用结构:它们的行为与赋值下的“正常对象”(引用类型)不同当作为参数传递时,这可能导致意外行为;如果查看代码的人不知道他们正在处理一个结构。它们不能被继承。将结构作为参数传递比类更昂贵。

✔️ 考虑结构使用

创建一个对象或不需要创建该对象(您可以直接赋值,它创建对象)需要提高速度或性能无需施工人员和拆卸人员(静态承包商可用)不需要类继承,但可以接受接口工作负载小的对象工作,如果工作负载高,内存问题将增加不能为变量设置默认值。结构还可以使用方法、事件、静态构造函数、变量等GC中的工作量更少不需要引用类型,只需要值类型(每次创建新对象时)无不可变对象(字符串是不可变对象,因为任何操作都不会在不更改原始字符串的情况下每次返回新字符串)

根据C#语言规范:

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实例存储在数组中。

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

使用类,两个变量可以引用同一个对象,因此对一个变量的操作可能会影响另一个变量引用的对象。对于结构,每个变量都有自己的数据副本,对其中一个变量的操作不可能影响另一个变量。例如,以下代码片段产生的输出取决于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参数之外,不可能创建对结构的引用,这在许多情况下排除了它们的使用。