结构与类

我即将在代码中创build100,000个对象。 他们是小的,只有2或3个属性。 我将把它们放在一个通用列表中,当它们出现时,我将循环它们并检查值a ,也许更新值b

创build这些对象作为类或结构是更快/更好?

编辑

一个。 属性是值types(除了我认为的string?)

湾 他们可能(我们还不确定)有一个validation方法

编辑2

我想知道:堆上的对象和垃圾收集器处理的堆栈是一样的,还是工作不同?

创build这些对象作为类或结构更快吗?

你是唯一可以确定这个问题的答案的人。 testing一个有意义的,以用户为中心的相关性能指标,然后您就会知道这个变化是否对相关场景中的真实用户产生了有意义的影响。

结构消耗更less的堆内存(因为它们更小 ,更容易压缩,而不是因为它们在堆栈中)。 但是,他们需要更长的时间来复制参考副本。 我不知道你的性能指标是什么内存使用率或速度; 这里有一个权衡,你是知道这是什么的人。

创build这些对象作为类或结构是更好吗?

也许类,也许结构。 作为一个经验法则:如果对象是:

2.逻辑上是一个不可变的值
3.有很多
然后我会考虑把它做成一个结构。 否则,我会坚持一个参考types。

如果你需要改变一个结构体的某个字段,通常最好是构build一个构造器,返回一个正确设置了字段的整个新结构体。 这可能稍微慢一点(衡量它!),但逻辑上更容易推理。

垃圾收集器堆和堆栈上的对象是否相同?

,它们不一样,因为堆栈中的对象是集合的根源 。 垃圾收集器不需要问“堆栈上的这个东西还活着吗?” 因为这个问题的答案总是“是的,它在堆栈上”。 (现在,你不能依靠这个来维持一个对象的存活,因为堆栈是一个实现细节,允许抖动引入优化,比如说注销通常是堆栈值的东西,然后它就不会堆栈所以GC不知道它还活着,一个注册对象可以让它的后代积极地收集,只要注册的对象不会被再次读取)。

但是垃圾收集不得不将堆栈中的对象视为活动对象,就像对待任何已知活着的对象一样。 堆栈中的对象可以引用需要保持活动状态的堆分配对象,因此为了确定活动集,GC必须将堆对象(例如活的堆分配对象)对待。 但是显然他们不是为了压缩堆把它当作“活物”,因为它们不是堆在首位。

这清楚吗?

有时用struct你不需要调用new()构造函数,并直接分配使得它更快的领域。

例:

 Value[] list = new Value[N]; for (int i = 0; i < N; i++) { list[i].id = i; list[i].is_valid = true; } 

大约是2到3倍

 Value[] list = new Value[N]; for (int i = 0; i < N; i++) { list[i] = new Value(i, true); } 

其中Value是一个具有两个字段(id和is_valid)的结构。

另一方面是项目需要被移动或select的值types,所有的复制都会减慢你的速度。 为了得到确切的答案,我怀疑你必须剖析你的代码并进行testing。

列表将返回结构的副本。 更新它将需要从列表中删除它,并再次添加,创build一个全新的新值,并将其分配给列表索引。

因此,最好是使用类。

相关阅读: 为什么可变结构“邪恶”?

结构看起来可能类似于类,但是有一些重要的区别,你应该知道。 首先,类是引用types,结构是值types。 通过使用结构,您可以创build类似于内置types的对象,并享受其好处。

当你在一个类上调用New操作符时,它将被分配在堆上。 但是,当你实例化一个结构体时,它会在堆栈上被创build。 这将会带来性能上的提升。 另外,你不会像使用类一样处理对一个结构实例的引用。 你将直接与结构实例工作。 因此,在将一个结构体传递给一个方法时,它是通过值传递的,而不是作为参考。

更多在这里:

http://msdn.microsoft.com/en-us/library/aa288471(VS.71).aspx

结构数组在连续的内存块中表示在堆中,而对象数组则表示为与堆中其他地方的实际对象本身相邻的引用块,因此需要内存用于对象和数组引用。

在这种情况下,当你把它们放在一个List<> (并且一个List<>被放到一个数组上)时,使用结构会更有效率和记忆。

(但是要小心,大型数组会在大对象堆上find自己的路,如果它们的生命周期很长,可能会对进程的内存pipe理产生不利影响,请记住,内存不是唯一的考虑因素。

如果他们有价值的语义,那么你应该使用一个结构。 如果他们有引用语义,那么你应该使用一个类。 有一些例外,即使有价值语义,也主要倾向于创build一个类,但是从那里开始。

至于你的第二次编辑,GC只处理堆,但堆栈空间比堆栈空间多,所以把东西放在堆栈上并不总是一个胜利。 除此之外,结构types的列表和types的列表都将在堆上,所以这在这种情况下是不相关的。

编辑:

我开始认为邪恶这个词是有害的。 毕竟,如果不是主动需要的话,创build一个可变类是一个坏主意,我不会排除使用可变结构。 这是一个糟糕的主意,因此几乎总是一个坏主意,但大多数情况下,它只是不符合值的语义,所以在给定的情况下使用结构是没有意义的。

私有嵌套结构可能有一些合理的例外,因此结构的所有用途都被限制在一个非常有限的范围内。 这不适用于此。

真的,我认为“它变了,所以这是一个坏的东西”并不比堆和栈(至less确实有一些性能影响,即使是经常被歪曲的堆栈)更好。 “它发生了变化,所以认为它具有价值语义是不合理的,所以它是一个糟糕的结构”只是稍有不同,但是我认为重要。

最好的解决办法是测量,再测量一次,再测量一些。 有可能是你在做什么的细节,可能会使一个简单,简单的答案,如“使用结构”或“使用类”困难。

一个结构本质上是一个领域的集合。 在.NET中,一个结构可能“伪装”成为一个对象,对于每一种结构types,.NET都隐含地定义了一个堆对象types,它具有与堆对象相同的字段和方法,其行为就像一个对象。 保存对这种堆对象的引用(“盒式”结构)的variables将显示引用语义,但是直接保存结构的variables仅仅是variables的集合。

我认为很多结构与类的混淆源于这样一个事实,即结构有两种截然不同的用例,它们应该有非常不同的devise准则,但是MS准则并没有区分它们。 有时需要一些行为像一个物体的东西; 在这种情况下,MS指南是相当合理的,尽pipe“16字节限制”应该更像24-32。 然而有时候,需要的是variables的聚合。 用于此目的的结构应该只包含一堆公共字段,可能还有一个Equals覆盖, ToString覆盖和IEquatable(itsType).Equals实现。 用作字段聚合的结构不是对象,不应该假装为。 从结构的angular度来看,领域的意义应该不过是“写给这个领域的最后一件事”。 任何额外的含义应由客户代码确定。

例如,如果一个可变聚合结构体具有成员MinimumMaximum ,则结构体本身不应承诺Minimum <= Maximum 。 接收这样一个结构作为参数的代码应该像通过单独的MinimumMaximumMinimum不大于Maximum的要求应该被认为是Minimum参数不大于单独通过的Maximum

有时候需要考虑的有用模式是让ExposedHolder<T>类定义如下:

 class ExposedHolder<T> { public T Value; ExposedHolder() { } ExposedHolder(T val) { Value = T; } } 

如果有一个List<ExposedHolder<someStruct>> ,其中someStruct是一个variables聚合结构,可以做一些事情,比如myList[3].Value.someField += 7; ,但给myList[3].Value其他代码将给它的Value的内容,而不是给它一个方法来改变它。 相比之下,如果使用List<someStruct> ,则需要使用var temp=myList[3]; temp.someField += 7; myList[3] = temp; var temp=myList[3]; temp.someField += 7; myList[3] = temp; 。 如果使用可变类types, myList[3]的内容暴露给外部代码将需要将所有字段复制到其他对象。 如果使用了不可变类types或“对象样式”结构,则需要构造一个新的实例,就像myList[3]除了someField是不同的,然后将该新实例存储到列表中。

另外一个注意事项:如果你存储了大量类似的东西,最好将它们存储在可能嵌套的结构数组中,最好试图保持每个数组大小在1K到64K之间。 结构数组是特殊的,因为索引结构可以直接引用内部结构,所以可以说“a [12] .x = 5”。 尽pipe可以定义类似于数组的对象,但是C#不允许它们与数组共享这样的语法。

从c ++的angular度来看,我同意修改一个结构的属性比一个类更慢。 但是我认为由于结构被分配在堆栈而不是堆上,他们会更快地读取数据。 从堆中读取数据需要比堆栈更多的检查。

使用类。

一般说明。 为什么不在创build它们时更新值b?

那么,如果你用struct去完成,那么就去掉string,并使用固定大小的字符或字节缓冲区。

那就是:performance。