不可变类vs结构

Geo*_*kov 20 c# struct class immutability

以下是类与C#中的结构不同的唯一方法(如果我错了请纠正我):

  • 类变量是引用,而struct变量是值,因此struct的整个值被复制到赋值和参数传递中
  • 类变量是存储在堆栈上的指针,指向堆上的内存,而struct变量作为值存储在堆上

假设我有一个不可变的结构,即包含初始化后无法修改的字段的结构.每次我将此结构作为参数传递或在赋值中使用时,该值都将被复制并存储在堆栈中.

然后假设我使这个不可变的结构成为一个不可变的类.此类的单个实例将创建一次,并且只有对类的引用将被复制到赋值和参数传递中.

如果对象是可变的,则这两种情况下的行为会有所不同:当一个人改变对象时,在第一种情况下,结构的副本将被修改,而在第二种情况下,原始对象将被改变.但是,在这两种情况下,对象都是不可变的,因此这个对象的用户实际上是类还是结构没有区别.

由于复制引用比复制struct便宜,为什么要使用不可变结构?

此外,由于可变结构是邪恶的,看起来根本没有理由使用结构.

我哪里错了?

Ree*_*sey 30

由于复制引用比复制struct便宜,为什么要使用不可变结构?

这并非总是如此.在64位操作系统上复制引用将是8个字节,这可能比许多结构更大.

另请注意,创建类可能更昂贵.创建结构通常完全在堆栈上完成(尽管有很多例外),这非常快.创建类需要创建对象句柄(用于垃圾收集器),在堆栈上创建引用以及跟踪对象的生命周期.这可能会增加GC压力,这也是一个真正的成本.

话虽这么说,创建一个大的不可变结构可能不是一个好主意,这是为什么在类和结构之间进行选择指南建议总是使用类,如果你的结构将超过16个字节,如果它将被装箱,和其他使差异变小的问题.

话虽如此,我经常将我的决定更多地放在有关类型的预期用途和含义上.值类型应该用于引用单个值(同样,参考指南),并且通常具有语义含义和预期用法不同于类.在类或结构之间进行选择时,这通常与性能特征同样重要.

  • `完全在堆栈上创建结构可能不是真的.[在桌面CLR上的Microsoft C#实现中,当值是局部变量或临时不是lambda或匿名方法的封闭局部变量时,值类型存储在堆栈中,并且方法体不是迭代器块,抖动选择不注册值](http://blogs.msdn.com/b/ericlippert/archive/2010/09/30/the-truth-about-value-types.aspx) (5认同)
  • 还值得一提的是,结构和类对用户来说是否具有语义含义,是否真的是单个值,值得考虑.你不应该*只考虑性能. (4认同)

Eri*_*ert 23

里德的答案非常好,但只是增加几点:

如果我错了,请纠正我

你基本上走在正确的轨道上.您已经犯了将变量混淆的常见错误.变量是存储位置; 值存储在变量中.而你正在调整常见的神话,即"价值类型在堆栈上"; 相反,变量去任何短期长期储存,因为变量的存储位置.变量是短期还是长期存储取决于其已知的寿命,而不是其类型.

但所有这些与你的问题并不特别相关,问题归结为要求驳斥这种三段论:

  • 可变结构是邪恶的.
  • 引用复制比结构复制便宜,因此不可变结构总是更糟.
  • 因此,结构永远不会有任何用途.

我们可以用几种方式驳斥三段论.

首先,是的,可变的结构是邪恶的.但是,它们有时非常有用,因为在某些有限的情况下,您可以获得性能优势.除非已经用尽其他合理途径并且存在真正的性能问题,否则我不推荐这种方法.

其次,引用复制不一定比结构复制便宜.引用通常实现为4或8字节的托管指针(尽管这是一个实现细节;它们可以实现为不透明的句柄).复制引用大小的结构既不比复制引用大小的引用更便宜也更昂贵.

第三,即使引用复制比结构复制便宜,也必须取消引用引用才能到达它们的字段.解除引用不是零成本!它不仅需要机器周期来取消引用引用,这样做可能会破坏处理器缓存,这可能使未来的解引用更加昂贵!

第四,即使参考复制比结构复制便宜,谁在乎呢?如果这不是产生不可接受的性能成本的瓶颈,那么哪一个更快就完全无关紧要.

第五,参考文献在内存空间中远比结构更昂贵.

第六,引用增加了费用,因为引用网络必须由垃圾收集器定期跟踪; 垃圾收集器可以完全忽略"blittable"结构.垃圾收集费用很高.

第七,与引用类型不同,不可变值类型不能为null.你知道每个价值都是一个很好的价值.正如Reed所指出的,为了获得引用类型的良好值,您必须运行分配器和构造函数.那不便宜.

第八,值类型代表值,程序通常是关于值的操纵.用一种语言"烘焙""价值"和"参考"的隐喻是有道理的,无论哪种"更便宜".