结构与课程

时间:2010-10-15 13:37:37

标签: c# struct

我即将在代码中创建100,000个对象。它们很小,只有2或3个属性。我将它们放在通用列表中,当它们出现时,我将循环它们并检查值a并可能更新值b

将这些对象创建为类还是结构更快/更好?

修改

一个。属性是值类型(我认为字符串除外?)

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

编辑2

我想知道:堆上的对象和堆栈是否由垃圾收集器同等处理,或者它的工作方式是否有所不同?

10 个答案:

答案 0 :(得分:126)

  

更快将这些对象创建为类还是结构?

您是唯一可以确定该问题答案的人。尝试两种方式,衡量有意义的,以用户为中心的相关性能指标,然后您就会知道更改是否会对相关方案中的真实用户产生有意义的影响。

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

  

更好将这些对象创建为类还是结构?

也许是阶级,也许是结构。根据经验: 如果对象是:
1.小
2.逻辑上一个不可变的值
他们中有很多人 然后我会考虑把它变成一个结构。否则我会坚持使用引用类型。

如果你需要改变一个struct的某个字段,通常最好构建一个构造函数,该构造函数返回一个正确设置字段的整个新结构。这可能稍微慢一些(测量它!)但在逻辑上更容易推理。

  

垃圾收集器是否对堆和堆栈上的对象进行了相同的处理?

,它们不一样,因为堆栈上的对象是集合的根。垃圾收集器不需要问“堆栈中的这个东西是活着的吗?”因为这个问题的答案总是“是的,它在堆栈上”。 (现在,您不能依赖它来保持一个对象,因为堆栈是一个实现细节。允许抖动引入优化,例如,注册通常是堆栈值,然后它永远不会在堆栈上,所以GC不知道它仍然存在。一个注册对象可以积极收集它的后代,只要不再读取寄存器上的寄存器。)

但是垃圾收集器确实必须将堆栈上的对象视为活动,就像它将任何已知存活的对象视为活着一样。堆栈上的对象可以引用需要保持活动的堆分配对象,因此GC必须将堆栈对象视为活动堆分配对象,以便确定实时集。但显然,为了压缩堆,它们被视为“活动对象”,因为它们首先不在堆上。

这是清楚的吗?

答案 1 :(得分:22)

有时使用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);
}

其中Valuestruct,其中包含两个字段(id和is_valid)。

另一方面,需要移动项目或选择值类型,所有复制都会降低您的速度。为了得到确切的答案,我怀疑你必须分析你的代码并测试它。

答案 2 :(得分:7)

结构可能看起来与类相似,但是您应该注意一些重要的区别。首先,类是引用类型,结构是值类型。通过使用结构,您可以创建行为类似于内置类型的对象,并享受它们的好处。

当您在类上调用New运算符时,它将在堆上分配。但是,当您实例化一个结构时,它会在堆栈上创建。这将带来性能提升。此外,您将不会像处理类那样处理对结构实例的引用。您将直接使用struct实例。因此,在将结构传递给方法时,它是通过值而不是作为引用传递的。

更多信息:

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

答案 3 :(得分:6)

结构数组在连续的内存块中表示在堆上,而对象数组表示为连续的引用块,实际对象本身位于堆的其他位置,因此需要对象的内存和他们的数组引用。

在这种情况下,当您将它们放在List<>中(并且List<>被备份到数组上)时,使用结构将在内存方面更有效。

(请注意,大型数组会在大对象堆上找到它们的方式,如果它们的生命周期很长,可能会对进程的内存管理产生负面影响。请记住,内存不是唯一的考虑因素。 )

答案 4 :(得分:4)

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

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

编辑:

我开始认为 evil 一词有害。毕竟,如果没有主动需要,使类可变是一个坏主意,并且我不排除使用可变结构。这是一个糟糕的想法,但几乎总是一个坏主意,但大多数情况下它只是与值语义不一致所以在给定的情况下使用结构是没有意义的。

私有嵌套结构可能存在合理的异常,因此该结构的所有使用都限制在非常有限的范围内。这不适用于此。

真的,我认为“它变异所以这是一个糟糕的结果”并不比继续关于堆和堆栈(至少确实会产生一些性能影响,即使经常被误传的那些)要好得多。 “它变异了,所以很可能没有意义将其视为具有值语义,所以它是一个糟糕的结构”只是略有不同,但重要的是我认为。

答案 5 :(得分:3)

最佳解决方案是再次测量,测量,然后测量更多。你可能会有一些细节可能会使“使用结构”或“使用类”等简单易用的答案变得困难。

答案 6 :(得分:3)

结构本质上只是一个字段集合。在.NET中,结构可以“假装”为对象,对于每种结构类型,.NET隐式定义具有相同字段和方法的堆对象类型 - 作为堆对象 - 将像对象一样运行。保存对这种堆对象(“盒装”结构)的引用的变量将显示引用语义,但直接保存结构的变量只是变量的聚合。

我认为结构与类之间的混淆很大程度上源于这样一个事实,即结构有两个非常不同的使用案例,它们应该有非常不同的设计指南,但MS指南不区分它们。有时候需要一种表现得像物体的东西;在这种情况下,MS指南非常合理,尽管“16字节限制”可能更像是24-32。然而,有时需要的是变量的集合。用于此目的的结构应该只包含一堆公共字段,可能还有Equals覆盖,ToString覆盖和IEquatable(itsType).Equals实现。用作字段聚合的结构不是对象,不应该假装。从结构的角度来看,字段的含义应该不过是“写到这个字段的最后一件事”。任何其他含义应由客户代码确定。

例如,如果变量聚合结构具有成员MinimumMaximum,则结构本身不应该承诺Minimum <= Maximum。接收这样的结构作为参数的代码应该表现得好像它是通过单独的MinimumMaximum值传递的。 Minimum不大于Maximum的要求应被视为要求Minimum参数不大于单独传递的Maximum参数。

有时需要考虑的一个有用模式是将ExposedHolder<T>类定义为:

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

如果其中一个List<ExposedHolder<someStruct>>,其中someStruct是一个变量聚合结构,可以执行myList[3].Value.someField += 7;之类的操作,但将myList[3].Value提供给其他代码会给它Value的内容,而不是给它一个改变它的方法。相反,如果使用List<someStruct>,则需要使用var temp=myList[3]; temp.someField += 7; myList[3] = temp;。如果使用可变类类型,将myList[3]的内容暴露给外部代码则需要将所有字段复制到其他对象。如果一个人使用了不可变类类型或“对象样式”结构,那么有必要构建一个类似于myList[3]的新实例,除了不同的someField,然后存储新实例实例进入列表。

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

答案 7 :(得分:1)

使用课程。

总的来说。为什么不在创建时更新值b?

答案 8 :(得分:1)

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

答案 9 :(得分:1)

好吧,如果你使用struct afterall,那么去除字符串并使用固定大小的char或字节缓冲区。

那就是:表现。