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

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

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

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


当前回答

结构是值类型。如果将结构分配给新变量,则新变量将包含原始变量的副本。

public struct IntStruct {
    public int Value {get; set;}
}

执行以下操作将导致存储在内存中的结构的5个实例:

var struct1 = new IntStruct() { Value = 0 }; // original
var struct2 = struct1;  // A copy is made
var struct3 = struct2;  // A copy is made
var struct4 = struct3;  // A copy is made
var struct5 = struct4;  // A copy is made

// NOTE: A "copy" will occur when you pass a struct into a method parameter.
// To avoid the "copy", use the ref keyword.

// Although structs are designed to use less system resources
// than classes.  If used incorrectly, they could use significantly more.

类是引用类型。将类分配给新变量时,该变量包含对原始类对象的引用。

public class IntClass {
    public int Value {get; set;}
}

执行以下操作只会导致内存中类对象的一个实例。

var class1 = new IntClass() { Value = 0 };
var class2 = class1;  // A reference is made to class1
var class3 = class2;  // A reference is made to class1
var class4 = class3;  // A reference is made to class1
var class5 = class4;  // A reference is made to class1  

结构可能会增加代码错误的可能性。如果将值对象视为可变引用对象,那么当所做的更改意外丢失时,开发人员可能会感到惊讶。

var struct1 = new IntStruct() { Value = 0 };
var struct2 = struct1;
struct2.Value = 1;
// At this point, a developer may be surprised when 
// struct1.Value is 0 and not 1

其他回答

C#或其他.net语言中的结构类型通常用于保存应该表现为固定大小的值组的内容。结构类型的一个有用方面是,可以通过修改保存结构类型实例的存储位置来修改该实例的字段,而不是以其他方式。可以以这样的方式对结构进行编码,即变异任何字段的唯一方法是构造一个完整的新实例,然后使用结构赋值通过用新实例中的值覆盖目标的所有字段来对其进行变异,但除非结构不提供创建其字段具有非默认值的实例的方法,如果结构本身存储在可变位置,则其所有字段都是可变的。

请注意,如果结构包含一个私有类类型字段,那么可以设计一个结构类型,使其基本上表现为类类型,并将其自身成员重定向到包装的类对象的成员。例如,PersonCollection可能提供财产SortedByName和SortedById,这两个属性都持有对PersonCollection的“不可变”引用(在其构造函数中设置),并通过调用creator.GetNameSortedEnumerator或creator.GetIdSortedEnumerator来实现GetEnumerater。此类结构的行为与对PersonCollection的引用非常相似,除了它们的GetEnumerator方法将绑定到PersonCollection中的不同方法。也可以有一个结构来包裹数组的一部分(例如,可以定义一个ArrayRange<T>结构,该结构将保存一个称为Arr的T[]、一个int Offset和一个int Length,以及一个索引属性,对于范围0到Length-1的索引idx,该属性将访问Arr[idx+Offset])。不幸的是,如果foo是这种结构的只读实例,当前的编译器版本将不允许像foo[3]+=4这样的操作;因为它们无法确定这些操作是否会尝试写入foo的字段。

也可以设计一个结构,使其行为类似于一个值类型,该值类型包含一个可变大小的集合(无论何时该结构都会被复制),但唯一可行的方法是确保该结构包含引用的对象不会暴露于任何可能使其发生变异的对象。例如,可以有一个类似数组的结构,它保存一个私有数组,其索引的“put”方法创建一个新数组,其内容与原始数组的内容相似,只有一个元素发生了更改。不幸的是,要使这种结构有效地执行可能有些困难。虽然有时结构语义可能很方便(例如,能够将类似数组的集合传递给例程,调用方和被调用方都知道外部代码不会修改集合,这可能比要求调用方和受调用方防御性地复制它们所提供的任何数据要好),类引用指向永远不会变异的对象的要求通常是一个相当严格的约束。

.NET支持值类型和引用类型(在Java中,只能定义引用类型)。引用类型的实例在托管堆中分配,并且在没有未完成的引用时被垃圾收集。另一方面,值类型的实例是在堆栈中分配的,因此一旦其作用域结束,分配的内存就会被回收。当然,值类型通过值传递,引用类型通过引用传递。除了System.String之外,所有C#原语数据类型都是值类型。

何时在类上使用结构,

在C#中,结构是值类型,类是引用类型。您可以使用enum关键字和struct关键字在C#中创建值类型。使用值类型而不是引用类型将导致托管堆上的对象减少,从而减少垃圾收集器(GC)的负载,减少GC周期,从而提高性能。然而,价值类型也有其缺点。传递一个大结构肯定比传递一个引用成本更高,这是一个明显的问题。另一个问题是与装箱/拆箱相关的开销。如果您想知道装箱/拆箱是什么意思,请按照以下链接了解装箱和拆箱的详细说明。除了性能之外,有时您只需要类型具有值语义,如果引用类型是您的全部,那么这将很难实现(或者很难看)。当您需要复制语义或需要自动初始化时(通常在这些类型的数组中),您应该只使用值类型。

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

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

类最适合将复杂的操作和数据分组在一起这将在整个项目中发生变化;结构是更好的选择大多数情况下保持不变的简单对象和数据。除了它们的用途之外,它们在一个键上有根本的不同即变量之间传递或分配的方式。类是引用类型,这意味着它们由参考结构是值类型,这意味着它们是由价值

小心使用类。如果您有一些引用相同内存的游戏对象,修改其中一个将修改其他对象。

创建结构对象时,其所有数据都存储在没有引用或连接到其内存的对应变量地方这使得结构对于创建需要快速高效地复制,同时保留独立的身份。

ExampleStruct struct1= new ExampleStruct()
ExampleStruct struct2= struct1

修改结构2不会影响结构1。

基本上,创建结构是为了提高性能。但是,由于涉及到所有的复制,有时结构可能会更慢。如果结构有很多需要复制的变量,那么将其转换为类并传递引用可能会更快如果您有一个结构数组,那么数组本身就是堆上的一个对象,结构值包含在数组中。所以垃圾收集器只有一个对象需要考虑。如果数组超出范围,垃圾收集器可以在一个步骤中释放数组中的所有结构。如果代码的任何其他部分正在使用此数组中的结构,由于结构被复制,因此我们可以安全地释放数组本身及其内容。如果您有一个对象数组,那么数组本身和数组中的每个对象都是堆上的独立对象。每个对象都可以存储在堆的完全不同的部分,而代码的另一部分可能会引用这些对象。因此,当我们的数组超出范围时,我们无法立即释放数组。因为垃圾收集器必须单独考虑每个对象,并确保在取消分配之前没有对每个对象的引用。

我的规则是

1、始终使用类;

如果有任何性能问题,我会根据@IAbstract提到的规则将一些类更改为结构,然后进行测试,看看这些更改是否可以提高性能。