什么时候应该在C#中使用结构而不是类?我的概念模型是,当项只是值类型的集合时,使用结构。一种将它们逻辑地结合在一起的方法。
我在这里遇到了这些规则:
结构应表示单个价值结构应具有内存占用空间小于16字节。结构不应在之后更改创造
这些规则有效吗?结构在语义上意味着什么?
什么时候应该在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参数之外,不可能创建对结构的引用,这在许多情况下排除了它们的使用。
其他回答
除了常见的性能差异之外,让我再补充一个方面,那就是默认值的使用意图。
如果其字段的默认值不表示建模概念的合理默认值,请不要使用结构。
Eg.
即使所有字段都设置为默认值,“颜色”或“点”也有意义。RGB 0,0,0是一种非常好的颜色,(0,0)作为2D中的点也是如此。但是Address或PersonName没有合理的默认值。我的意思是,你能理解FirstName=null和LastName=null的PersonName吗?
如果你用一个类实现了一个概念,那么你可以强制执行某些不变量,例如一个人必须有名字和姓氏。但对于结构,总是可以创建一个实例,将其所有字段设置为默认值。
因此,当对没有合理默认值的概念进行建模时,更喜欢类。类的用户会明白null意味着没有指定PersonName,但如果您给他们一个PersonName结构实例,并将其所有财产设置为null,他们会感到困惑。
(通常的免责声明:性能考虑可能会凌驾于此建议之上。如果您有性能问题,请在决定解决方案之前进行衡量。试试BenchmarkDotNet,这很好!)
第一:互操作场景或需要指定内存布局时
第二:当数据大小与引用指针几乎相同时。
类最适合将复杂的操作和数据分组在一起这将在整个项目中发生变化;结构是更好的选择大多数情况下保持不变的简单对象和数据。除了它们的用途之外,它们在一个键上有根本的不同即变量之间传递或分配的方式。类是引用类型,这意味着它们由参考结构是值类型,这意味着它们是由价值
小心使用类。如果您有一些引用相同内存的游戏对象,修改其中一个将修改其他对象。
创建结构对象时,其所有数据都存储在没有引用或连接到其内存的对应变量地方这使得结构对于创建需要快速高效地复制,同时保留独立的身份。
ExampleStruct struct1= new ExampleStruct()
ExampleStruct struct2= struct1
修改结构2不会影响结构1。
基本上,创建结构是为了提高性能。但是,由于涉及到所有的复制,有时结构可能会更慢。如果结构有很多需要复制的变量,那么将其转换为类并传递引用可能会更快如果您有一个结构数组,那么数组本身就是堆上的一个对象,结构值包含在数组中。所以垃圾收集器只有一个对象需要考虑。如果数组超出范围,垃圾收集器可以在一个步骤中释放数组中的所有结构。如果代码的任何其他部分正在使用此数组中的结构,由于结构被复制,因此我们可以安全地释放数组本身及其内容。如果您有一个对象数组,那么数组本身和数组中的每个对象都是堆上的独立对象。每个对象都可以存储在堆的完全不同的部分,而代码的另一部分可能会引用这些对象。因此,当我们的数组超出范围时,我们无法立即释放数组。因为垃圾收集器必须单独考虑每个对象,并确保在取消分配之前没有对每个对象的引用。
除了运行时直接使用的值类型和其他用于PInvoke的值类型之外,您只能在两种情况下使用值类型。
当您需要复制语义时。当您需要自动初始化时,通常在这些类型的数组中。
简单地说,如果:
您的对象财产/字段不需要更改。我的意思是你只想给它们一个初始值,然后读它们。对象中的财产和字段是值类型,它们并没有那么大。
如果是这种情况,您可以利用结构来获得更好的性能和优化的内存分配,因为它们只使用堆栈,而不是同时使用堆栈和堆(在类中)