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

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

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

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


当前回答

神话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#深度第三版

其他回答

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

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

神话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#深度第三版

我刚刚在处理Windows Communication Foundation[WCF]命名管道,我注意到使用Structs确实有意义,以确保数据交换是值类型而不是引用类型。

类是引用类型。创建类的对象时,分配给该对象的变量只保留对该内存的引用。将对象引用指定给新变量时,新变量将引用原始对象。通过一个变量所做的更改反映在另一个变量中,因为它们都引用了相同的数据。结构是值类型。创建结构时,分配给该结构的变量保存该结构的实际数据。当将结构分配给新变量时,将复制该结构。因此,新变量和原始变量包含相同数据的两个单独副本。对一个副本所做的更改不会影响另一个副本。通常,类用于建模更复杂的行为,或在创建类对象后要修改的数据。结构最适合于主要包含在创建结构后不打算修改的数据的小型数据结构。

类和结构(C#编程指南)

在需要使用StructLayoutAttribute显式指定内存布局(通常用于PInvoke)的情况下,需要使用“结构”。

编辑:注释指出,可以将类或结构与StructLayoutAttribute一起使用,这当然是正确的。在实践中,您通常会使用一个结构——它是在堆栈上分配的,而不是在堆上分配的——如果您只是向非托管方法调用传递一个参数,这是有意义的。