在结构体中存储引用类型

7

假设我有一个非常简单的C#结构体

   public struct foo{
   public  int a{get;set;}
   public  int b{get;set;}
   public  int c{get;set;}
   public  int d{get;set;}
   public  string hello {get;set;}
   }

我理解上述方法比使用class更加“高效”?

如果我继续添加字符串属性,从什么时候开始应该将结构体转换为类?

编辑:我计划在一个以GDI为中心的应用程序中传递这个结构体。我一直认为当主要处理值类型时,结构体的性能更好。


1
为什么这样会更有效率?试图获取您目前所知道的内容,但在大多数使用情况下并不是如此。 - Dykam
当你说它比使用类更有效率时,你能否请进行解释和证明?为什么? - Muhammad Maqsoodur Rehman
1
在这个上下文中,你对“高效”的定义是什么? - Daniel Mann
2
你应该从一个类开始,并且在99.99%的情况下始终使用类。请参阅https://dev59.com/elXTa4cB1Zd3GeqP4Ln9 - Meta-Knight
请查看以下网址:https://dev59.com/P3RB5IYBdhLWcg3wxZ1K 和 https://dev59.com/P3RB5IYBdhLWcg3wxZ1K。 - Raj Ranjhan
显示剩余5条评论
5个回答

34

我认为使用这种方式比使用类更高效,是吗?

绝对不是。那是一个很糟糕的结构体。结构体应该是小的, 不超过一个引用的四倍大小; 在32位机器上,你的结构体大小等于五个引用的大小。结构体应该代表值;这似乎没有代表任何值。结构体应该是不可变的;这里充满了可变性。

如果我继续添加字符串属性,那么在什么时候将结构体转换为类?

将此结构转换为类的时刻是创建它的时候。它一开始就不应该是一个结构体。您默认的假设应该是类已经足够好;只有当您有证据表明使用结构体解决了您实际存在的问题时,才转向结构体。

我计划在 GDI - centric 应用程序中传递此结构体。我一直认为处理主要是值类型时,结构体更具性能。

结构体比类更有效的情况非常少:当它们是小的、不可变的、表示值,并由其他值(如整数)组成时。在其他情况下,它们的效率低下:因为结构体按值复制,所以大型结构体使用起来可能比引用慢得多。因为结构体按值复制,可变结构体会导致错误,因为您在想要修改变量时实际上是在修改副本。因为仅包含其他结构体的结构体可以被垃圾收集器完全跳过,所以结构体仅在不包含引用时对于清除目的更有效。

无论如何,判断某个东西是否更加高效的方式是尝试两种方法并测量其性能与您的性能目标之间的差距使用科学。设定一个目标并衡量它。仅因为听说结构体“更有效率”而做出技术决策是不明智的。测量,然后你就会知道什么更有效率。


感谢您详细的回复,Eric。 您对 System.Drawing.Rectangle 有何看法?虽然这个结构体不包含引用类型,但它确实包含大量值类型。 - maxp
5
@maxp:我不想让结构体比那个大很多,事实上它是可变的,这让人困扰。但是,可以说它的行为类似于值类型。此外,可能会创建大量矩形,例如在数组中,因此它们不太可能被单独复制很多次。通常通过传递包含矩形的某种引用类型来传递矩形。涉及图形的任何东西的性能要求都相当高;你应该避免收集压力。如果有测量数据支持,将其设为结构体是一个合理的选择。 - Eric Lippert
结构体应该尽可能小,不要超过引用的四倍大小。有没有描述这句话的链接呢?你默认应该认为类已经足够好了;只有在确实存在通过使用结构体解决问题的证据时才转向结构体。你会在什么情况下推荐使用结构体呢? - Pankaj
@PankajGarg:关于您的第一个请求:如果您不相信我的话,那么您为什么会相信其他人的话呢?假设我告诉您阅读Cwalina的书;那么你为什么不问要一个参考呢?关于您的第二个问题:当您代表一个小的不可变值,最好由其他值组成时,请使用结构体。 - Eric Lippert
天啊,现在我真想删除我的回答。 - 3Dave

5
如果您需要类所提供的增强功能(例如继承),那么请使用类。如果不需要,那么结构体可能会更加“轻巧”,但除非您预计会出现某些性能问题(例如在具有大量迭代的紧密循环中进行垃圾回收时),否则每当您想要通过ref传递结构体以使方法进行修改等,都会产生不必要的工作(尽管在这个例子中,销毁具有引用类型属性的结构体也会导致GC)。
实际影响是:使用结构体还是类取决于您的用例,而不是您拥有的属性数量。
有关类和结构体之间差异及相对优劣的良好解释,请参见此MSDN文章
欲了解Eric Lippert关于垃圾回收、结构体和类的优秀说明,请参见他对此问题的回答

1
我想知道你是否可以详细解释一下结构体可以更“轻便”的想法?我认为这就是 OP 得出它们更有效率的想法之所在,但是使用四个整数时,结构体在许多方面已经比类更“重”。 - StriplingWarrior
@StriplingWarrior 很好的问题;这是我一段时间前学到的东西,但我不记得从哪里学来的了。我会阅读一些资料并更新我的答案。 - 3Dave
从存活根结构中移除一个结构体本身不可能触发GC(除非在框架中有意编写此类代码,这将是疯狂的)。分配实例到长期存储器会触发GC(当要放置实例的空间不足时)。 - ShuggyCoUk
2
@StriplingWarrior:每个类实例在32位机器上至少有12字节的开销,或者在64位机器上有24字节的开销。如果将要创建一个类型的新实例并传递它们,那么结构体通常比类等效体更轻量级。 - supercat

1

我认为问题不在于你是否持续添加字符串,而在于你想用这个对象做什么。结构体是值类型,类是引用类型,就我所知,结构体有一些性能上的优势(在内存堆栈分配方面),但最终还是取决于你对对象要做什么。

我想我曾经读过一篇文章,说结构体非常适合短暂存在的大量对象,但我可能记错了。


0
如果有人这样说:
  foo x[100];
  foo[1] = foo[2];
  foo[1].a += 5;
那么后面的语句是否会影响foo[2].a?如果foo是一个结构体类型,它不会;如果是类类型,它会。
与Lippert先生所说的相反,您的类型作为结构体可能完全没问题,但有几个注意事项:
  1. 结构体应该直接暴露其字段,而不是通过属性来访问,除非属性中有一些非平凡的逻辑。
  2. 传递大于16字节的按值结构体比传递较小的结构体要慢得多,因为.NET对较小的结构体有特殊处理。但是,通过引用传递不受结构体大小的影响。
  3. 持有可变引用类型的可变结构体通常表现出一种奇怪的可变值语义和可变引用语义的混合。然而,对于持有不可变引用类型(如`string`)的结构体来说,这不是一个问题。
  4. 结构体类型的属性通常很难处理。只读引用类型的属性有时更容易处理,但是用一个同样占用20字节数据的类替换一个将持有20字节数据的结构体,在32位系统上几乎会使存储需求翻倍,在64位系统上甚至会超过两倍。

可变值类型的语义是有用的。旧版C#编译器(多年前)在处理它们时存在一些问题,有些人希望阻止人们学习理解它们,但这些都不是在适当的情况下使用它们的好理由。


-3

请记住,您在结构体中声明的任何内容默认都是公共的。如果您希望数据被隐藏,则必须使用类。除此之外,结构体中没有面向对象编程的所有典型特征。

顺便提一下:类(计算机编程)


结构体与类一样受到访问修饰符的限制,其属性也是如此。明确声明所有实体为public/private/internal等也是一个好习惯。 - 3Dave
4
这是关于C#而不是C++的问题。 - Eric Lippert

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接