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

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

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

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


当前回答

不,我不完全同意这些规则。它们是考虑性能和标准化的良好指南,但不是根据可能性。

正如你在回答中看到的,有很多创造性的方法来使用它们。因此,这些指导原则需要做到这一点,始终是为了性能和效率。

在本例中,我使用类以更大的形式表示真实世界的对象,使用结构来表示具有更精确用途的较小对象。你说的是,“一个更具凝聚力的整体”。类将是更多面向对象的元素,而结构可以具有这些特性,尽管规模较小。IMO。

我在Treeview和Listview标签中经常使用它们,在这些标签中可以非常快速地访问常见的静态属性。我一直在努力以另一种方式获取这些信息。例如,在我的数据库应用程序中,我使用Treeview,其中包含表、SP、函数或任何其他对象。我创建并填充我的结构,将其放入标记中,将其拉出,获取所选内容的数据等等。我不会在课堂上这样做!

我确实会尽量缩小它们,在单实例情况下使用它们,并防止它们发生变化。注意内存、分配和性能是明智的。测试是非常必要的。

其他回答

我很少对事物使用结构。但这只是我,这取决于我是否需要对象为空。

如其他答案所述,我对真实世界的对象使用类。我也有结构用于存储少量数据的想法。

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

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

无论何时:

不需要多态性,want值语义,以及希望避免堆分配和相关的垃圾收集开销。

然而,需要注意的是,结构(任意大)传递比类引用(通常是一个机器字)更昂贵,因此类在实践中可能会更快。

OP引用的消息来源有一定的可信度。。。但微软呢?对结构使用的立场是什么?我向微软寻求了一些额外的学习,以下是我的发现:

如果类型很小,通常寿命很短,或者通常嵌入其他对象。除非类型具有以下所有特征,否则不要定义结构:它在逻辑上表示单个值,类似于基本类型(整数、双精度等)。它的实例大小小于16字节。它是不可变的。它不必经常装箱。

Microsoft一贯违反这些规则

好吧,无论如何,第二和第三。我们喜爱的字典有两个内部结构:

[StructLayout(LayoutKind.Sequential)]  // default for structs
private struct Entry  //<Tkey, TValue>
{
    //  View code at *Reference Source
}

[Serializable, StructLayout(LayoutKind.Sequential)]
public struct Enumerator : 
    IEnumerator<KeyValuePair<TKey, TValue>>, IDisposable, 
    IDictionaryEnumerator, IEnumerator
{
    //  View code at *Reference Source
}

*参考源

“JonnyCantCode.com”的消息源得到了4分之3的结果,这是可以原谅的,因为第4名可能不会成为问题。如果您发现自己正在装箱一个结构,请重新思考您的体系结构。

让我们来看看为什么微软会使用这些结构:

每个结构Entry和Enumerator表示单个值。速度条目永远不会作为Dictionary类之外的参数传递。进一步的调查表明,为了满足IEnumerable的实现,Dictionary使用了每次请求枚举器时都会复制的枚举器结构。。。有道理。Dictionary类的内部。枚举器是公共的,因为Dictionary是可枚举的,并且必须对IEnumerator接口实现(例如IEnumeratorgetter)具有同等的可访问性。

更新-此外,请注意,当一个结构实现了一个接口(如Enumerator)并被强制转换为该实现的类型时,该结构将成为一个引用类型并被移动到堆中。在Dictionary类内部,Enumerator仍然是值类型。然而,一旦方法调用GetEnumerator(),就会返回一个引用类型IEnumerator。

我们在这里没有看到任何保持结构不可变或保持实例大小仅为16字节或更少的尝试或证明:

上面的结构中没有任何内容声明为只读-不是不可变的这些结构的大小可能远远超过16字节条目具有未确定的生存期(从Add()到Remove()、Clear()或垃圾收集);

和4.两个结构都存储TKey和TValue,我们都知道它们非常适合作为引用类型(添加了额外的信息)

尽管有哈希键,但字典速度很快,部分原因是实例化结构比引用类型更快。这里,我有一个Dictionary<int,int>,它存储了300000个随机整数和顺序递增的键。

容量:312874内存大小:2660827字节完成调整大小:5ms填充总时间:889ms

容量:必须调整内部数组大小之前可用的元素数。

MemSize:通过将字典序列化为MemoryStream并获得字节长度(对于我们的目的来说足够精确)来确定。

完成调整大小:将内部数组从150862个元素调整为312874个元素所需的时间。如果您认为每个元素都是通过Array.CopyTo()顺序复制的,那就不太糟糕了。

填充总时间:由于日志记录和我添加到源中的OnResize事件,确实存在偏差;然而,在操作期间填充300k个整数并调整大小15次仍然令人印象深刻。只是出于好奇,如果我已经知道容量,那么总的填充时间是多少?13毫秒

那么,现在,如果Entry是一个类呢?这些时间或指标真的会有那么大的不同吗?

容量:312874内存大小:2660827字节完成调整大小:26ms填充总时间:964ms

显然,最大的区别在于调整大小。如果字典是用容量初始化的,有什么区别吗?不够关心。。。12毫秒。

所发生的是,由于Entry是一个结构,它不需要像引用类型那样进行初始化。这既是价值类型的美,也是价值类型的祸根。为了使用Entry作为引用类型,我必须插入以下代码:

/*
 *  Added to satisfy initialization of entry elements --
 *  this is where the extra time is spent resizing the Entry array
 * **/
for (int i = 0 ; i < prime ; i++)
{
    destinationArray[i] = new Entry( );
}
/*  *********************************************** */  

我必须将Entry的每个数组元素初始化为引用类型的原因可以在MSDN:Structure Design中找到。简而言之:

不要为结构提供默认构造函数。如果结构定义了默认构造函数结构,公共语言运行时自动对每个数组元素执行默认构造函数。某些编译器(如C#编译器)不允许结构具有默认构造函数。

这其实很简单,我们可以借用阿西莫夫的《机器人三定律》:

结构必须安全才能使用结构必须有效地执行其功能,除非这会违反规则#1结构在使用过程中必须保持完整,除非需要对其进行销毁以满足规则#1

…我们从中得到了什么:简而言之,对价值类型的使用负责。它们快速高效,但如果维护不当(即无意复制),则能够导致许多意外行为。

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

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