关于c#:结构的不可变性

Immutability of structs

本问题已经有最佳答案,请猛点这里访问。

Possible Duplicate:
Why are mutable structs evil?

我在很多地方读过它,包括这里,最好使结构不可变。

这背后的原因是什么?我看到许多微软创建的结构是可变的,就像XNA中的结构一样。或许BCL中还有更多。

不遵守这一准则有哪些利弊?


结构应表示值。值不变。数字12是永恒的。

但是,考虑:

1
2
3
4
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.Enumerator是一个结构。


我经常在我的(性能关键的)项目中使用可变结构——而且我不会遇到问题,因为我了解复制语义的含义。据我所知,人们提倡不可变结构的主要原因是,不理解其含义的人不会陷入麻烦。

这不是一件可怕的事情,但我们现在正处于它成为"福音真理"的危险之中,事实上,有时它是使结构可变的最佳选择。就像所有事情一样,规则也有例外。


您已经询问了不遵循结构应该是不可变的准则的利弊。

缺点:现有的答案很好地涵盖了缺点,并且描述的大多数问题都是由于相同的原因——由于结构的值语义而导致的意外行为。

优点:使用可变结构的主要优点是性能。显然,这个建议附带了所有关于优化的常见警告:确保您的部分代码需要优化,并确保任何更改确实通过分析优化了代码的性能。

有关讨论何时使用可变结构的优秀文章,请参阅RicoMariani的基于值编程的性能测试(或者更具体地说,答案)。


技术上的原因是可变结构似乎能够做他们实际上不做的事情。由于设计时语义与引用类型相同,因此开发人员会感到困惑。此代码:

1
2
3
4
public void DoSomething(MySomething something)
{
    something.Property = 10;
}

根据MySomethingstruct还是class的情况,其行为有很大不同。对我来说,这是一个令人信服的理由,但不是最令人信服的理由。如果您查看ddd的value对象,就可以看到如何处理结构的连接。DDD中的值对象可以最好地表示为.NET中的值类型(因此也是结构)。因为它没有身份,所以不能改变。

从你的住址这样的角度来考虑这个问题。您可以"更改"您的地址,但地址本身没有更改。事实上,你有一个新的地址分配给你。从概念上讲,这是可行的,因为如果你真的改变了地址,你的室友也必须搬家。


没有比可变结构更便宜的操作了,这就是为什么你经常在高性能代码中看到它,比如图形处理例程。

不幸的是,可变结构不能很好地处理对象和属性,修改stuct的副本而不是结构本身太容易了。因此,它们不适用于大多数代码。

P.S.为了避免复制可变结构的成本,通常将它们存储并传递到数组中。


结构通常应该表示某种单一的统一。因此,改变一个值的属性没有多大意义,如果你想要一个不同的值,那么创造一个全新的值就更有意义了。

当使用不可变结构时,语义变得更简单,并且您可以避免这样的陷阱:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 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;

结果是列表中的结构根本没有更改。表达式间隔[0]从列表中复制结构的值,然后更改临时值的属性,但该值永远不会放回列表中。

编辑:将示例更改为使用列表而不是数组。


您应该使结构不可变的原因是它们是值类型,这意味着每次将它们传递给方法时都会复制它们。

因此,例如,如果您有一个返回结构的属性,那么修改该结构上字段的值将是毫无价值的,因为getter将返回该结构的副本,而不是对该结构的引用。我在代码中看到过这种情况,通常很难理解。

如果您设计的结构是不可变的,那么您可以帮助程序员避免这些错误。


当复制周围的结构时,会复制它们的内容,因此如果修改复制的版本,"原始"将不会更新。

这是错误的来源,因为即使您知道自己陷入了复制结构(只是通过将结构传递给方法)和修改副本的陷阱。

上周又发生在我身上,让我找了一个小时的虫子。

保持结构不变可防止…

除此之外,首先需要确保有一个很好的理由来使用结构-"优化"或"我想要在堆栈上快速分配的东西"不算作答案。在您依赖布局的地方进行封送或处理—好的,但是您通常不应该将这些结构保留很长时间,它们是值而不是对象。