可能重复:
为什么可变结构是邪恶的?
我在很多地方都读过它,包括这里,最好将结构设为不可变。
这背后的原因是什么?我看到许多 Microsoft 创建的可变结构,例如 xna 中的结构。BCL 中可能还有更多。
不遵守本指南的利弊是什么?
可能重复:
为什么可变结构是邪恶的?
我在很多地方都读过它,包括这里,最好将结构设为不可变。
这背后的原因是什么?我看到许多 Microsoft 创建的可变结构,例如 xna 中的结构。BCL 中可能还有更多。
不遵守本指南的利弊是什么?
结构应该代表值。值不会改变。数字 12 是永恒的。
但是,请考虑:
Foo foo = new Foo(); // a mutable struct
foo.Bar = 27;
Foo foo2 = foo;
foo2.Bar = 55;
现在 foo.Bar 和 foo2.Bar 是不同的,这常常是出乎意料的。特别是在属性等场景中(幸运的是编译器检测到了这一点)。还有收藏品等;你如何明智地改变它们?
可变结构的数据丢失太容易了。
最大的缺点是事情的表现并不像您期望的那样 - 特别是如果可变性来自直接值和其中的引用类型的混合。
老实说,当人们使用可变结构时,我不记得我看到人们在新闻组中提出的所有奇怪问题 - 但这些原因肯定存在。可变结构会导致问题。远离。
编辑:我刚刚找到了一封我不久前写的关于这个主题的电子邮件。它详细说明了一点:
这在哲学上是错误的:结构应该代表某种基本价值。这些基本上是不可变的。您无法更改数字 5。您可以将变量的值从 5 更改为 6,但逻辑上您不会更改值本身。
这实际上是一个问题:它创造了很多奇怪的情况。如果它通过接口是可变的,那就特别糟糕了。然后您可以开始更改装箱值。伊克。我看过很多新闻组帖子,这些帖子是由于人们试图使用可变结构并遇到问题。例如,我看到了一个非常奇怪的 LINQ 示例,它失败List<T>.Enumerator
了,因为它是一个结构。
我经常在我的(性能关键的)项目中使用可变结构 - 我没有遇到问题,因为我了解复制语义的含义。据我所知,人们提倡不可变结构的主要原因是让不了解其含义的人不会陷入困境。
这并不是一件可怕的事情——但我们现在面临着它成为“福音真理”的危险,而事实上,有时它是使结构可变的最佳选择。与所有事物一样,规则也有例外。
没有什么比可变结构更便宜的了,这就是为什么你经常在高性能代码中看到它,比如图形处理例程。
不幸的是,可变结构不能很好地处理对象和属性,修改结构的副本而不是结构本身太容易了。因此它们不适用于您的大多数代码。
PS 为了避免复制可变结构的成本,它们通常以数组的形式存储和传递。
技术原因是可变结构似乎能够做他们实际上不做的事情。由于设计时语义与引用类型相同,这让开发人员感到困惑。这段代码:
public void DoSomething(MySomething something)
{
something.Property = 10;
}
行为完全不同,具体取决于MySomething
是 astruct
还是 a class
。对我来说,这是一个令人信服的理由,但不是最令人信服的理由。如果您查看DDD的Value Object,您可以看到与应该如何处理结构的联系。DDD 中的值对象可以最好地表示为 .Net 中的值类型(因此是结构)。因为没有身份,所以无法改变。
根据您的地址来考虑这一点。您可以“更改”您的地址,但地址本身并没有改变。事实上,您已经分配了一个新地址。从概念上讲,这是可行的,因为如果你真的改变了你的地址,你的室友也必须搬家。
您已经询问了不遵循结构应该是不可变的准则的利弊。
缺点:现有答案中很好地涵盖了缺点,并且描述的大多数问题都是由于相同的原因 -由于结构的值语义导致的意外行为。
优点:使用可变结构的主要优点是性能。显然,这个建议带有关于优化的所有常见警告:确保您的部分代码需要优化,并确保任何更改确实通过分析优化了代码的性能。
有关讨论何时可能需要使用可变结构的优秀文章,请参阅 Rico Mariani 的基于值编程的性能测验(或更具体地说,答案)。
一个结构通常应该代表某种单一的统一体。因此,更改值的属性之一没有多大意义,如果您想要一个以某种方式不同的值,则创建一个全新的值更有意义。
使用不可变结构时,语义会变得更简单,并且可以避免这样的陷阱:
// a struct
struct Interval {
int From { get; set; }
int To { get; set; }
}
// create a list of structs
List<Interval> intervals = new List<Interval>();
// add a struct to the list
intervals.Add(new Interval());
// try to set the values of the struct
intervals[0].From = 10;
intervals[0].To = 20;
结果是列表中的结构根本没有改变。表达式 Interval[0] 从列表中复制结构的值,然后更改临时值的属性,但该值永远不会放回列表中。
编辑:将示例更改为使用列表而不是数组。
当您复制周围的结构时,您会复制它们的内容,因此如果您修改复制的版本,“原始”将不会被更新。
这是错误的来源,因为即使您知道自己陷入了复制结构(只需将其传递给方法)并修改副本的陷阱。
上周又发生在我身上,让我花了一个小时寻找错误。
保持结构不可变可以防止...
除此之外,您首先需要确保有充分的理由使用结构 - “优化”或“我想要在堆栈上快速分配的东西”不算作答案。编组或依赖于布局的东西 - 好的,但通常不应该将这些结构保留很长时间,它们是值而不是对象。
您应该使结构不可变的原因是它们是ValueTypes,这意味着每次将它们传递给方法时都会复制它们。
因此,例如,如果您有一个返回结构的属性,那么修改该结构上字段的值将毫无价值,因为 getter 将返回该结构的副本,而不是对该结构的引用。我已经在代码中看到了这一点,而且通常很难捕捉到。
如果您为不变性设计结构,您可以帮助程序员避免这些错误。