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

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

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

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


当前回答

根据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参数之外,不可能创建对结构的引用,这在许多情况下排除了它们的使用。

其他回答

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

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

结构在大多数方面类似于类/对象。结构可以包含函数、成员,并且可以继承。但C#中的结构仅用于数据保存。结构比类占用更少的RAM,并且垃圾收集器更容易收集。但当您在结构中使用函数时,编译器实际上采用的结构与类/对象非常相似,所以如果您想要使用函数,请使用类/对象。

除了运行时直接使用的值类型和其他用于PInvoke的值类型之外,您只能在两种情况下使用值类型。

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

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

类和结构(C#编程指南)

结构适合于数据的原子表示,其中所述数据可以被代码复制多次。克隆对象通常比复制结构更昂贵,因为它涉及分配内存、运行构造函数以及在完成时释放/垃圾回收。