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

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

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

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


当前回答

以下是在Microsoft网站上定义的规则:

✔️ 如果类型的实例很小且通常很短,或者通常嵌入在其他对象中,请考虑定义结构而不是类。

❌ 避免定义结构,除非该类型具有以下所有特征:

它在逻辑上表示单个值,类似于原始类型(int、double等)。

它的实例大小小于16字节。

它是不可变的。

它不必经常装箱。

供进一步阅读

其他回答

除了常见的性能差异之外,让我再补充一个方面,那就是默认值的使用意图。

如果其字段的默认值不表示建模概念的合理默认值,请不要使用结构。

Eg.

即使所有字段都设置为默认值,“颜色”或“点”也有意义。RGB 0,0,0是一种非常好的颜色,(0,0)作为2D中的点也是如此。但是Address或PersonName没有合理的默认值。我的意思是,你能理解FirstName=null和LastName=null的PersonName吗?

如果你用一个类实现了一个概念,那么你可以强制执行某些不变量,例如一个人必须有名字和姓氏。但对于结构,总是可以创建一个实例,将其所有字段设置为默认值。

因此,当对没有合理默认值的概念进行建模时,更喜欢类。类的用户会明白null意味着没有指定PersonName,但如果您给他们一个PersonName结构实例,并将其所有财产设置为null,他们会感到困惑。

(通常的免责声明:性能考虑可能会凌驾于此建议之上。如果您有性能问题,请在决定解决方案之前进行衡量。试试BenchmarkDotNet,这很好!)

根据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#结构是类的轻量级替代。它可以做的几乎与类相同,但使用结构而不是类的“成本”更低。这样做的原因有点技术性,但总而言之,类的新实例放在堆上,新实例化的结构放在堆栈上。此外,您不是像处理类那样处理对结构的引用,而是直接处理结构实例。这也意味着,当您将结构传递给函数时,它是按值传递的,而不是作为引用。在关于函数参数的章节中有更多关于这一点的内容。

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

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

在这方面,你列出的三条准则对我来说并不有用。当我需要以特殊顺序写出400字节的内容时,我将定义一个400字节的结构,并用它应该具有的任何不相关的值填充它,我将以当时最合理的方式设置它。(好吧,四百字节会很奇怪——但当我以写Excel文件为生的时候,我处理的是多达四十字节的结构,因为这就是一些BIFF记录的大小。)

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

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