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

San*_*eep 57 .net c# immutability

我只是想知道为什么结构,字符串等是不可变的?是什么原因使它们成为不可变的,其余的对象是可变的.有什么东西被认为是使对象不可变的?

对可变和不可变对象分配和释放内存的方式有什么不同吗?

Eri*_*ert 116

如果这个主题让你感兴趣,我在http://blogs.msdn.com/b/ericlippert/archive/tags/immutability/上有很多关于不可变编程的文章.

我只是想知道为什么结构,字符串等是不可变的?

默认情况下,结构和类不是不可变的,尽管使结构不可变是最佳实践.我也喜欢不可变的课程.

字符串是不可变的.

是什么原因使它们成为不可变的,其余的对象是可变的.

使所有类型不可变的原因:

  • 关于不改变的对象更容易推理.如果我有一个包含三个项目的队列,我知道它现在不是空的,五分钟前它不是空的,将来也不会是空的.这是不可改变的!一旦我了解了它的事实,我就可以永远使用这个事实.关于不可变对象的事实不会过时.

  • 第一点的特例:不可变对象更容易使线程安全.大多数线程安全问题是由于在一个线程上写入而在另一个线程上读取; 不可变对象没有写入.

  • 不可变对象可以拆开并重新使用.例如,如果你有一个不可变的二叉树,那么你可以使用它的左右子树作为不同树的子树而不用担心它.在可变结构中,您通常最终会复制数据以重复使用它,因为您不希望更改一个逻辑对象而影响另一个逻辑对象.这可以节省大量的时间和内存.

使结构不可变的原因

有很多理由使结构不可变.这只是一个.

结构按值复制,而不是通过引用复制.很容易意外地将结构视为通过引用复制.例如:

void M()
{
    S s = whatever;
    ... lots of code ...
    s.Mutate();
    ... lots more code ...
    Console.WriteLine(s.Foo);
    ...
}
Run Code Online (Sandbox Code Playgroud)

现在,您想要将一些代码重构为辅助方法:

void Helper(S s)
{
    ... lots of code ...
    s.Mutate();
    ... lots more code ...
}
Run Code Online (Sandbox Code Playgroud)

错误!这应该是(参考S) - 如果你不这样做,那么变异将发生在s 的副本上.如果你不首先允许突变,那么所有这些问题都会消失.

使字符串不可变的原因

还记得我关于不可变结构保留事实的事实的第一点吗?

假设字符串是可变的:

public static File OpenFile(string filename)
{
    if (!HasPermission(filename)) throw new SecurityException();
    return InternalOpenFile(filename);
}
Run Code Online (Sandbox Code Playgroud)

如果敌对主叫名变异的安全检查和之前的文件被打开?代码刚刚打开了一个他们可能没有权限的文件!

同样,可变数据很难推理.您希望"此调用者有权查看此字符串描述的文件"这一事实永远是真实的,直到发生突变为止.使用可变字符串,为了编写安全代码,我们必须不断地制作我们知道不会改变的数据副本.

有什么东西被认为是使对象不可变的?

这种类型在逻辑上代表了一种"永恒"的价值吗?数字12是12; 它不会改变.整数应该是不可变的.点(10,30)是点(10,30); 它不会改变.积分应该是不可改变的.字符串"abc"是字符串"abc"; 它不会改变.字符串应该是不可变的.清单(10,20,30)没有变化.等等.

有时候,类型代表了改变的事物.玛丽史密斯的姓是史密斯,但明天她可能是玛丽琼斯.或史密斯小姐今天可能是明天的史密斯医生.外星人现在有五十个健康点,但在被激光束击中后有十点.有些东西最能代表突变.

对可变和不可变对象分配和释放内存的方式有什么不同吗?

不是这样的.正如我之前提到的,关于不可变值的一个好处是,你可以重复使用它们的一部分,而无需复制.所以从这个意义上说,内存分配可能会非常不同.

  • @gigi你*不要*购买每本书的第二份副本.相反,当你添加第三本书时,你现在有四个堆栈.空堆栈,堆栈在空堆栈顶部有一本书,堆栈上面有一本书,堆栈上面有一本书.正是因为你*不想*购买更多不可变的书籍!它是需要复制的可变数据结构.你完全倒退了. (6认同)
  • @EricLippert让我们说你有一堆书.如果你想在它们之上放置另一本书,那么将书放在它们之上更自然,而不是购买每本书的副本,按照相同的顺序堆叠它们,然后放置新书除此之外.诸如集合之类的对象通常会发生变化,并且将它们视为不可变的东西并不直观. (4认同)
  • 我认为这就是他们所说的Crystal Clear ...感谢您提供如此详细的解释...... (2认同)
  • @EricLippert我现在看到了光明.谢谢,你永远是一位很棒的老师. (2认同)
  • @NDUF:让我换种说法。假设您拥有ABC堆栈,并且您想同时拥有ABCX和ABCY堆栈。如果堆栈ABC是* mutable *,那么您必须进行复印,将X推到原件上,将Y推到副本上。如果堆栈ABC是不可变的,那么当您创建两个新堆栈时,您只需将* reference *复制到ABC。您不必复制整个堆栈数据结构。具有此属性的结构称为“持久”。 (2认同)

Jus*_*ner 9

结构不是......这就是为什么可变结构是邪恶的.

创建可变结构可能会导致应用程序中的各种奇怪行为,因此,它们被认为是一个非常糟糕的主意(源于它们看起来像引用类型但实际上是值类型的事实,并且每当您通过时都会被复制他们身边).

另一方面,字符串是.这使它们本身具有线程安全性,并允许通过字符串实习进行优化.如果你需要动态构造一个复杂的字符串,你可以使用StringBuilder.


Han*_*ant 5

结构类型不是不可变的。是的,字符串是。使您自己的类型不可变很容易,只需不提供默认构造函数,将所有字段设为私有,并且不定义更改字段值的方法或属性。让一个应该改变对象的方法返回一个新对象。有一个内存管理的角度,你往往会创建大量的副本和垃圾。


sup*_*cat 5

可变性和不变性的概念在应用于结构和类时具有不同的含义。可变类的一个关键方面(通常是关键弱点)是如果Foo有一个Bartype字段List<Integer>,它保存对包含 (1,2,3) 的列表的引用,其他引用该列表的代码可以修改它,这样就Bar持有对包含 (4,5,6) 的列表的引用,即使其他代码无法访问Bar。相反,如果Foo有一个字段Biz类型System.Drawing.Point,只有这样,什么事情都可能修改的任何方面Biz拥有该领域的写访问

结构体的字段(公共和私有)可以被任何可以改变结构体存储位置的代码改变,并且不能被任何不能改变结构体存储位置的代码改变。如果结构体中封装的所有信息都保存在其字段中,则这样的结构体可以有效地将不可变类型的控制与可变类型的便利性结合起来,除非该结构体的编码方式消除了这种便利性(不幸的是,一些微软程序员推荐了一个习惯)。

结构体的“问题”在于,当在只读上下文(或不可变位置)中的结构体上调用方法(包括属性实现)时,系统会复制该结构体,在临时副本上执行该方法,然后静默丢弃结果。这种行为导致程序员提出了一个不幸的想法,即避免变异方法问题的方法是让许多结构禁止分段更新,而通过简单地用公开的字段替换属性可以更好地避免问题。

顺便说一句,有些人抱怨当类属性返回一个方便可变的结构时,对结构的更改不会影响它来自的类。我认为这是一件好事——返回的项目是一个结构的事实使行为变得清晰(特别是如果它是一个暴露的字段结构)。将使用假设结构和属性的片段Drawing.Matrix与使用 Microsoft 实现的该类的实际属性的片段进行比较:

// 假设结构
公共结构{
  公共浮动 xx,xy,yx,yy,dx,dy;
} Transform2d;

// “System.Drawing.Drawing2d.Matrix”的假设属性
公共 Transform2d 变换 {get;}

// “System.Drawing.Drawing2d.Matrix”的实际属性
公共浮动[]元素{获取;}

// 使用假设结构的代码
Transform2d myTransform = myMatrix.Transform;
myTransform.dx += 20;
...使用 myTransform 的其他代码

// 使用实际 Microsoft 属性的代码
float[] myArray = myMatrix.Elements;
myArray[4] += 20;
...使用 myArray 的其他代码

看看实际的微软属性,有没有办法判断写入是否myArray[4]会影响myMatrix?甚至看页面http://msdn.microsoft.com/en-us/library/system.drawing.drawing2d.matrix.elements.aspx有什么办法告诉吗?如果属性是使用基于结构的等价物编写的,就不会有混淆;返回结构的属性不会返回比六个数字的当前值更多或更少的值。更改myTransform.dx只不过是写入一个不附加任何其他东西的浮点变量。任何不喜欢改变myTransform.dx不会影响这一事实的人都myMatrix应该同样恼火写作myArray[4]不会影响myMatrix或者,不同的是的独立性myMatrixmyTransform是显而易见的,而独立myMatrixmyArray不。