结构的不变性
可能重复:
为什么可变的结构是邪恶的?
我在很多地方读过,包括在这里最好把结构做成不变的。
这背后的原因是什么? 我看到很多微软创build的结构都是可变的,就像xna中的结构一样。 在BCL中可能还有更多。
不遵循这个准则有什么优点和缺点?
结构应该代表价值 。 值不会改变。 数字12是永恒的。
但是,请考虑:
Foo foo = new Foo(); // a mutable struct foo.Bar = 27; Foo foo2 = foo; foo2.Bar = 55;
现在foo.Bar和foo2.Bar是不同的,这往往是意想不到的。 特别是在像属性的情况下(幸运的是编译器检测到这一点)。 但也收集等; 你怎么明智地改变它们?
数据丢失对于可变结构来说太容易了。
重要的是,事情不会如你所期望的那样行事 – 特别是如果可变性来自于直接价值和参照types的混合。
说实话,我不记得我看到人们在使用可变结构时在新闻组中遇到的所有奇怪问题 – 但是这些原因当然存在。 可变的结构会导致问题。 远离。
编辑:我刚刚发现了一个我刚才写的关于这个主题的电子邮件。 它只是略微阐述一下:
-
这在哲学上是错误的:一个结构应该代表某种基本的价值。 这些基本上是不可改变的。 您不能更改数字5.您可以将variables的值从5更改为6,但是您不会在逻辑上更改该值本身。
-
这实际上是一个问题:它造成了很多奇怪的情况。 如果通过一个接口可以改变,那就特别糟糕。 然后你可以开始改变盒装值。 伊克。 我见过很多新闻组post,这些post是由于人们试图使用可变结构而遇到问题。 我看到一个非常奇怪的LINQ例子,因为
List<T>.Enumerator
是一个结构,所以失败了。
我经常在我的(性能关键)项目中使用可变结构 – 而且我不会遇到问题,因为我理解复制语义的含义。 据我所知,人们主张不可改变的结构的主要原因是让那些不了解其含义的人不能自拔。
这并不是什么可怕的事情 – 但是现在我们陷入了“福音真理”的危险之中,事实上有时候合法地把结构变成可变的是最好的select。 像所有事情一样,这个规则也有例外。
没有什么比一个可变的结构更便宜的操作,这就是为什么你经常看到它像高性能的代码,如graphics处理例程。
不幸的是,可变结构不能很好地处理对象和属性,修改stuct的副本而不是结构本身就太简单了。 因此,他们不适合你的大部分代码。
PS为了避免复制可变结构的代价,通常将它们存储并传递给数组。
技术上的原因是可变结构似乎能够做他们实际上没有做的事情。 由于devise时语义与引用types相同,这使开发人员感到困惑。 此代码:
public void DoSomething(MySomething something) { something.Property = 10; }
如果MySomething
是一个struct
或一个class
,则行为会有很大不同。 对我来说,这是一个引人注目的,但不是最有说服力的理由。 如果你看DDD的价值对象 ,你可以看到结构如何对待的联系。 DDD中的值对象可以最好地表示为.Net中的值types(因此也是一个结构体)。 因为它没有身份,所以不能改变。
根据你的地址来想想这个。 你可以“改变”你的地址,但地址本身并没有改变。 事实上,你有一个新的地址分配给你。 从概念上讲,这是有效的,因为如果你真的改变了你的地址,你的室友也必须移动。
你已经要求不遵循这个结构的利弊,结构应该是不变的。
缺点:在现有答案中已经涵盖了这些缺点,而且大多数描述的问题都是由于同样的原因造成的 – 由于结构的价值语义而导致的意外行为 。
优点:使用可变结构的主要function可以是性能 。 显然,这个build议带有所有关于优化的通常的警告:确保你的代码的一部分需要优化,并确保任何改变实际上通过分析来优化你的代码的性能。
有关讨论何时可能使用可变结构的绝佳文章,请参阅Rico Mariani 关于基于价值的编程的性能testing (或更具体地说, 答案 )。
一个结构体通常应该代表某种单一的统一体。 因此,改变一个值的属性没什么意义,如果你想要一个不同的值,那么创build一个全新的值就更有意义了。
当使用不可变的结构时,语义变得更简单,并避免像这样的陷阱:
// 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;
结果是列表中的结构根本没有改变。 expression式Interval [0]从列表中复制struct的值,然后更改临时值的属性,但是该值永远不会放回到列表中。
编辑:更改了示例使用列表而不是数组。
当你复制你周围的结构时,复制它们的内容,所以如果你修改一个复制的版本,“原始”将不会被更新。
这是错误的来源,因为即使你知道你陷入了复制一个结构的陷阱(只是将它传递给一个方法)并修改了这个副本。
上周又发生在我身上,让我花了一个小时寻找一个bug。
保持结构不可变防止…
除此之外,您需要确保首先使用结构的真正理由 – “优化”或“我希望快速分配到堆栈上的东西”不算作答案。 编组或者你依赖布局的东西 – 好的,但是你通常不应该把这些结构保持很长时间,而是价值而不是对象。
你应该使结构不变的原因是它们是ValueTypes ,意味着每当你将它们传递给一个方法时,它们都被复制。
因此,例如,如果您有一个返回结构的属性,那么修改该结构上的字段的值将毫无价值,因为getter将返回结构的副本 ,而不是对结构的引用。 我已经看到这在代码中完成,而且往往很难赶上。
如果你devise的结构是不变的,你可以帮助程序员避免这些错误。