什么时候应该在C#中使用结构而不是类?我的概念模型是,当项只是值类型的集合时,使用结构。一种将它们逻辑地结合在一起的方法。
我在这里遇到了这些规则:
结构应表示单个价值结构应具有内存占用空间小于16字节。结构不应在之后更改创造
这些规则有效吗?结构在语义上意味着什么?
什么时候应该在C#中使用结构而不是类?我的概念模型是,当项只是值类型的集合时,使用结构。一种将它们逻辑地结合在一起的方法。
我在这里遇到了这些规则:
结构应表示单个价值结构应具有内存占用空间小于16字节。结构不应在之后更改创造
这些规则有效吗?结构在语义上意味着什么?
当前回答
不,我不完全同意这些规则。它们是考虑性能和标准化的良好指南,但不是根据可能性。
正如你在回答中看到的,有很多创造性的方法来使用它们。因此,这些指导原则需要做到这一点,始终是为了性能和效率。
在本例中,我使用类以更大的形式表示真实世界的对象,使用结构来表示具有更精确用途的较小对象。你说的是,“一个更具凝聚力的整体”。类将是更多面向对象的元素,而结构可以具有这些特性,尽管规模较小。IMO。
我在Treeview和Listview标签中经常使用它们,在这些标签中可以非常快速地访问常见的静态属性。我一直在努力以另一种方式获取这些信息。例如,在我的数据库应用程序中,我使用Treeview,其中包含表、SP、函数或任何其他对象。我创建并填充我的结构,将其放入标记中,将其拉出,获取所选内容的数据等等。我不会在课堂上这样做!
我确实会尽量缩小它们,在单实例情况下使用它们,并防止它们发生变化。注意内存、分配和性能是明智的。测试是非常必要的。
其他回答
类最适合将复杂的操作和数据分组在一起这将在整个项目中发生变化;结构是更好的选择大多数情况下保持不变的简单对象和数据。除了它们的用途之外,它们在一个键上有根本的不同即变量之间传递或分配的方式。类是引用类型,这意味着它们由参考结构是值类型,这意味着它们是由价值
小心使用类。如果您有一些引用相同内存的游戏对象,修改其中一个将修改其他对象。
创建结构对象时,其所有数据都存储在没有引用或连接到其内存的对应变量地方这使得结构对于创建需要快速高效地复制,同时保留独立的身份。
ExampleStruct struct1= new ExampleStruct()
ExampleStruct struct2= struct1
修改结构2不会影响结构1。
基本上,创建结构是为了提高性能。但是,由于涉及到所有的复制,有时结构可能会更慢。如果结构有很多需要复制的变量,那么将其转换为类并传递引用可能会更快如果您有一个结构数组,那么数组本身就是堆上的一个对象,结构值包含在数组中。所以垃圾收集器只有一个对象需要考虑。如果数组超出范围,垃圾收集器可以在一个步骤中释放数组中的所有结构。如果代码的任何其他部分正在使用此数组中的结构,由于结构被复制,因此我们可以安全地释放数组本身及其内容。如果您有一个对象数组,那么数组本身和数组中的每个对象都是堆上的独立对象。每个对象都可以存储在堆的完全不同的部分,而代码的另一部分可能会引用这些对象。因此,当我们的数组超出范围时,我们无法立即释放数组。因为垃圾收集器必须单独考虑每个对象,并确保在取消分配之前没有对每个对象的引用。
无论何时:
不需要多态性,want值语义,以及希望避免堆分配和相关的垃圾收集开销。
然而,需要注意的是,结构(任意大)传递比类引用(通常是一个机器字)更昂贵,因此类在实践中可能会更快。
✔️ 考虑结构使用
创建一个对象或不需要创建该对象(您可以直接赋值,它创建对象)需要提高速度或性能无需施工人员和拆卸人员(静态承包商可用)不需要类继承,但可以接受接口工作负载小的对象工作,如果工作负载高,内存问题将增加不能为变量设置默认值。结构还可以使用方法、事件、静态构造函数、变量等GC中的工作量更少不需要引用类型,只需要值类型(每次创建新对象时)无不可变对象(字符串是不可变对象,因为任何操作都不会在不更改原始字符串的情况下每次返回新字符串)
神话1:结构是轻量级类
这个神话有多种形式。有些人认为价值类型不能或不应该有方法或其他重要的行为,它们应该简单地使用数据传输类型,只有公共字段或简单的财产。DateTime类型是这是一个很好的反例:就存在而言,它是一种价值类型是有意义的一个基本单位,如数字或字符能够基于其值执行计算。从另一个角度看问题方向,数据传输类型通常应该是引用类型应该基于期望的值或引用类型语义,而不是类型。其他人认为价值类型比参考类型“更轻”性能。事实是,在某些情况下,价值类型更有表现力-它们不需要垃圾收集,除非它们是盒装的,没有类型例如,识别开销,并且不需要取消引用。但在其他方面方法,引用类型是性能更高的参数传递,将值分配给变量、返回值和类似操作只需要4或8个字节即可复制(取决于您运行的是32位还是64位CLR),而不是复制所有数据。想象一下,如果ArrayList是一个“纯”值类型,并且将ArrayList表达式传递给涉及复制其所有数据的方法!在几乎无论如何,性能并不是由这种决定决定的。瓶颈几乎永远不会出现在你认为会出现的地方,在你根据性能做出设计决策之前,你应该衡量不同的选择。值得注意的是,这两种信念的结合也不起作用。它不管一个类型有多少个方法(无论是类还是结构)-每个实例占用的内存不受影响。(内存方面有成本为代码本身占用,但这只发生一次,而不是每个实例。)
神话#2:引用类型存在于堆中;堆栈上存在值类型
这通常是由于重复的人的懒惰造成的部分是正确的,引用类型的实例总是在堆上创建的。这是导致问题的第二部分。正如我已经注意到的,变量的值存在于声明的任何地方,因此,如果您有一个类的实例变量类型为int,那么任何给定对象的变量值将始终位于该对象的其余数据的位置在堆上。仅局部变量(方法中声明的变量)和方法参数存在于堆栈中。在C#2和更高版本中,即使是一些局部变量在堆栈上生存,正如我们在第5章中研究匿名方法时所看到的那样。这些概念现在是否相关?如果您正在编写托管代码,那么应该让运行时考虑内存的最佳使用方式,这是有争议的。事实上,语言规范并不能保证哪里未来的运行时可能能够在堆栈上创建一些对象,如果或者C#编译器可以生成几乎不使用堆栈。下一个神话通常只是一个术语问题。
神话#3:默认情况下,对象在C中通过引用传递
这可能是流传最广的神话。再一次,制造这个的人声明经常(虽然不总是)知道C#的实际行为,但他们不知道“通过引用传递”的真正含义。不幸的是,对于那些知道这意味着什么。引用传递的形式定义相对复杂,涉及l值和类似的计算机科学术语,但重要的是,如果你通过了变量,您调用的方法可以通过更改其参数值来更改调用者变量的值。现在,请记住引用的值类型变量是引用,而不是对象本身。您可以更改参数引用的对象,而不通过引用传递参数本身。例如,以下方法更改StringBuilder的内容对象,但调用方的表达式仍将引用与之前:
void AppendHello(StringBuilder builder)
{
builder.Append("hello");
}
调用此方法时,参数值(对StringBuilder的引用)为通过值传递。如果要在方法,其中语句builder=null-改变不会是与神话相反。有趣的是,不仅神话中的“引用”部分不准确,而且“对象被传递”部分也不准确。对象本身也不会被传递通过引用或通过值。当涉及引用类型时通过引用传递,或者参数(引用)的值通过值传递。除此之外,这回答了当null为如果正在传递对象,则用作by-value参数,这将导致问题,因为不会有对象要通过!相反,空引用由传递值的方式与任何其他引用相同。如果这个简短的解释让您感到困惑,那么您可能想看看我的文章“在C#中传递参数”(http://mng.bz/otVt),其中包含更多内容细节这些神话并不是唯一的神话。拳击和拆箱是他们的我会努力澄清这一误解。
参考:Jon Skeet的C#深度第三版
在需要使用StructLayoutAttribute显式指定内存布局(通常用于PInvoke)的情况下,需要使用“结构”。
编辑:注释指出,可以将类或结构与StructLayoutAttribute一起使用,这当然是正确的。在实践中,您通常会使用一个结构——它是在堆栈上分配的,而不是在堆上分配的——如果您只是向非托管方法调用传递一个参数,这是有意义的。