只读结构体 vs 类

39

假设你只有不可变类型,并且你的代码已更新到 C# 7.3 版本,而且你的方法使用 in 关键字来作为输入参数,那么你为什么还要使用类而不是只使用只读结构体呢?

使用结构体的缺点是复制操作代价高昂,但是如果你防止任何复制(通过编译器保护性复制或代码中显式表示),只读结构体允许你只复制引用(与类相同),避免在堆上分配内存并减轻垃圾回收器的压力。

除了特殊情况(比如可能不适合放在栈上的非常大的对象),你通常会将只读结构体作为第一选择吗?

我感兴趣的是它们作为数据容器的情况。


1
因为您想通过引用访问和编辑同一对象? - MineR
12
首先,根据您想要的语义或引用语义选择structclass - Damien_The_Unbeliever
3
你采用了有限的使用案例,并试图基于它们提出一般性问题。引用类型不仅是数据容器。 - Dennis
1
结构体应该代表。所有具有相同字段值的结构体实例都是“相同的”和可互换的(例如,所有的5都是相同的)。类代表对象。它们具有固有的“身份”,不是其包含数据的一部分。即使您有两个具有相同数据的实例,对于某些考虑方式来说它们也不是“相同的”。 - Damien_The_Unbeliever
1
@jrh - 是的,它们在很多方面都会表现出这种方式。不过我还是可以用老牌的ReferenceEquals来区分它们 :-) - Damien_The_Unbeliever
显示剩余5条评论
1个回答

61

structs不应被看作“廉价对象”;它们具有类似的功能集,这些功能在某些方面重叠,在其他方面则是分离的。例如:

  • 结构体无法参与多态
  • 您不能将结构体视为接口实例而不进行装箱(注意:“约束调用”只适用于某些情况)
  • 很多库API 无法正常工作 (或可能根本不起作用)与结构体-它们期望可变的POCO;您可能会希望使用库从数据库中获取数据,序列化它,或在UI中呈现它-所有这些事情都会使结构体变得有点困难
  • 结构体无法与某些模式很好地配合使用,例如树形或同级关系(如果是结构体,则Foo不能包含Foo)-还有其他模式
  • 结构体和不可变类型一般来说使用起来可能会有些棘手

此外,请注意,直到最近(“ref返回”和“ref本地变量”),很难实现“readonly structs allow you to only copy the reference”的某些部分;现在这已经简单多了。

但坦白说,在大多数情况下,POCOs更容易使用,并且对于大多数应用程序代码场景都很好。

有时候,结构体确实是一个很好的选择。但并不是每次都是这样。我认为,如果你要使用struct,它应该要么是一个readonly struct(默认情况下),要么是一个ref struct(如果你知道为什么要这样做);可变的非ref结构体会导致痛苦。


所有的观点都是正确的,谢谢。如果我必须在没有任何适用情况的情况下进行选择,您是否建议默认选择只读结构体?除了上面提到的观点之外,您提到的那些情况会使它们难以使用? - user4388177
@user4388177,数据在代码中经常需要更改的现实是一个很大的问题 :) 特别是当数据在像List<T>这样的东西中时,它没有“ref return”API(也不能明智地这样做,因为如果有人调用Add并强制底层数组增长,人们会期望ref指向活动数组,但现有的ref实际上是内部指针进入数组)。这意味着您不能只是执行myRef = myRef.WithSomeChangedProperty("foo");(以覆盖该值)。 - Marc Gravell
这很有道理,但我越来越倾向于更加功能性/不可变的设计方法。当我必须创建一个类型并且想要强制其不可变性时,我认为只读结构体可能是一个很好的选择。列表和数组(即使使用不可变版本)大多数情况下都属于“大对象”类别,因此我不会想在那里使用结构体。 - user4388177
对于列表,如果你关心GC,你可能需要考虑使用池。详情请见此处(https://github.com/dotnet/corefx/issues/31492),搜索“Here's the gist”以获取实际使用的代码。 - Marc Gravell
是的,我们使用了ArrayPool,非常有用。谢谢。我不知道有一个关于列表池的GitHub问题,很有趣。尽管我们最终在生产中遇到了一些来自先前迭代的数据错误XD。 - user4388177
@user4388177 是的,你需要非常小心,不要在已经写好的代码之后再读取; 在这种情况下,列表实现会处理这个问题。 - Marc Gravell

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