为什么结构体不应该是可变的? C#

3
我读了When should I use a struct instead of a class?,其中引用了MSDN的指导,该指导表示:

除非类型具有以下所有特征,否则不要定义结构:

  • 它在逻辑上代表单个值,类似于原始类型(整数、双精度等)。
  • 它的实例大小小于16字节。
  • 它是不可变的。
  • 它不必频繁地装箱。

Why are mutable structs “evil”?有答案:

结构体是值类型,这意味着它们在传递时被复制。

我很难理解为什么我不应该只意识到结构体需要按引用传递,否则通过函数传递的版本所做的更改不会影响原始版本。是的,我以前遇到过很多此类错误,但现在我足够有经验,小心不让它发生。

我只在C#中制作游戏,并且游戏中的东西需要经常更改。

假设我有一个玩家角色,由我的游戏中的类“Player”表示。如果Player有变量X和Y来表示其位置,这些将经常更改。我不想每次移动时都创建一个新的Player,以保持不可变性?因此,我的类应该是可变的。好的。

但是,如果我想存储有关实体的数据。如果我在矩形(x、y、w、h)中存储其在屏幕上的位置,我应该将Rectangle设置为类,因为它将是可变的吗?它只是4个变量的逻辑集合,因此对我来说,结构体似乎是一个明智的容器。

我还有一个结构体“Colour”(r、g、b、a),但我将其设置为可变的,因为我可能想要移动游戏中的事物的alpha值以使它们淡入/淡出,或者在受伤时将某些东西变成红色,而无需调用“new Colour”每个渲染调用的开销。

我看到另一篇帖子说,如果满足以下条件,则应使用结构体:

  • 类型的主要责任是数据存储吗?
  • 它的公共接口是否完全由访问或修改其数据成员的属性定义?
  • 您确定您的类型永远不会有子类吗?
  • 您确定您的类型永远不会以多态方式处理吗?

对于我使用的结构体,这个答案是“是”,但其中大多数都是可变的。

我对所有这些相互矛盾的建议感到困惑。对于某些类型的使用,可变结构体是否可以接受,还是我的代码设计完全错误?


3
我发现大多数游戏引擎和游戏开发实践与其他应用的“最佳实践”存在性能上的冲突。可变结构体就是其中一个例子。 - Lukazoid
1
虽然这不仅限于结构体,但是拥有不可变的东西可以使多线程更容易进行。我可以想象会有一种情况是不好的。一旦你拥有了一个对象,在你下面它就不会改变,所以你可以在线程中传递它。还要考虑到商业应用程序通常被认为是许多用户执行“小”操作。 “最佳实践”集中在标准情况下; 当你像在计算密集型游戏中一样尽可能地提高性能时,有时需要创造性地解决问题。 - Jesus is Lord
@Lukazoid 好的,你的回答很有道理。但是这也给了我一个问题,那就是在游戏引擎的上下文中,我怎么知道哪些其他“最佳实践”应该被忽略? - Haighstrom
1
@hillin "结构体被分配在栈上而不是堆上" - 这并不总是正确的。var ds = new DateTime[1000]; 在堆上分配了1000个结构体。 - Asik
2
“new Colour”的“开销”并不是您想象的那样。对于值类型,“new Colour”只是从堆栈中分配内存,如果立即分配,则JIT可以优化出分配并将结果直接存储在更新的成员中。简而言之:继续使用“new Colour”和“new Rect”。这并没有您想象的那么糟糕。 - Raymond Chen
显示剩余6条评论
1个回答

3

这不仅仅是意外丢失信息的风险:

list.ForEach(item => item.X = 10);
// this code does nothing useful if item is a mutable struct

还有一个关于可变方法和readonly的奇怪交互:(参考链接)

readonly MutableStruct m_field;
...
m_field.MutatingMethod(); // mutates a temporary copy rather than the field

但是,如果通过分析确定以下情况:
  • 您无法使用引用类型,因为可能存在GC压力,或者想将所有对象放入数组以获得更好的局部性
  • 在修改结构时,您无法负担复制整个结构的开销
  • 不能合理地改变设计以解决这些问题(使结构体更小,拥有一个引用类型对象池等)
  • 您知道自己在做什么
那么可变的结构体可能就是您需要的。这也是它们被加入到语言中的原因。 说到游戏代码,SharpDX充满了可变的结构体(例如example)和通过引用传递的方法。

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