为什么 C# 结构体是不可变的?
我只是想知道为什么结构、字符串等是不可变的?使它们不可变而其余对象可变的原因是什么?哪些因素被认为使对象不可变?
可变和不可变对象的内存分配和释放方式有什么区别吗?
I was just curious to know why structs, strings etc are immutable? What is the reason for making them immutable and rest of the objects as mutable. What are the things that are considered to make an object immutable?
Is there any difference on the way how memory is allocated and deallocated for mutable and immutable objects?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
如果您对这个主题感兴趣,我在 https://ericlippert.com/2011/05/26/atomicity-volatility-and-immutability-are- Different-part-one/
默认情况下,结构和类不是不可变的,尽管最佳实践是使结构不可变。我也喜欢不可变的类。
字符串是不可变的。
使所有类型不可变的原因:
更容易推理不更改的对象。如果我有一个包含三个项目的队列,我知道它现在不空,五分钟前不空,将来也不会空。这是一成不变的!一旦我知道了一个事实,我就可以永远使用这个事实。关于不可变对象的事实不会过时。
第一点的特例:不可变对象更容易实现线程安全。大多数线程安全问题都是由于一个线程上的写入和另一个线程上的读取造成的;不可变对象没有写操作。
不可变的对象可以被拆开并重新使用。例如,如果您有一个不可变的二叉树,那么您可以使用它的左子树和右子树作为不同树的子树,而不必担心它。在可变结构中,您通常最终会复制数据以重新使用它,因为您不希望对一个逻辑对象的更改影响另一个逻辑对象。这可以节省大量时间和内存。
使结构不可变的原因
使结构不可变的原因有很多。这只是其中之一。
结构体是按值复制的,而不是按引用复制的。很容易意外地将结构视为通过引用复制。例如:
现在您想将其中一些代码重构为辅助方法:
错误!那应该是(ref S s)——如果你不这样做,那么突变将发生在 s 的副本上。如果你一开始就不允许突变,那么所有这些问题都会消失。
使字符串不可变的原因
还记得我关于不可变结构保留事实的第一点吗?
假设字符串是可变的:
如果恶意调用者在安全检查之后、文件打开之前改变文件名会怎样?该代码刚刚打开了一个他们可能无权访问的文件!
同样,可变数据很难推理。您希望“此调用者被授权查看此字符串描述的文件”这一事实永远为真,而不是直到发生突变。对于可变字符串,为了编写安全的代码,我们必须不断地复制我们知道不会更改的数据。
该类型在逻辑上是否代表“永恒”值?数字12就是数字12;它没有改变。整数应该是不可变的。点(10, 30)就是点(10, 30);它没有改变。点应该是不可变的。字符串“abc”就是字符串“abc”;它没有改变。字符串应该是不可变的。列表 (10, 20, 30) 不变。等等。
有时,类型代表确实发生变化的事物。玛丽·史密斯的姓是史密斯,但明天她可能会叫玛丽·琼斯。或者今天的史密斯小姐明天可能就是史密斯医生。外星人现在有五十点生命值,但被激光束击中后只剩下十点。有些事情最好用突变来表示。
不是这样的。正如我之前提到的,不可变值的好处之一是您可以重复使用它们的一部分而无需复制。所以从这个意义上说,内存分配可能非常不同。
If this subject interests you, I have a number of articles about immutable programming at https://ericlippert.com/2011/05/26/atomicity-volatility-and-immutability-are-different-part-one/
Structs and classes are not immutable by default, though it is a best practice to make structs immutable. I like immutable classes too.
Strings are immutable.
Reasons to make all types immutable:
It is easier to reason about objects that do not change. If I have a queue with three items in it, I know it is not empty now, it was not empty five minutes ago, it will not be empty in the future. It's immutable! Once I know a fact about it, I can use that fact forever. Facts about immutable objects do not go stale.
A special case of the first point: immutable objects are much easier to make threadsafe. Most thread safety problems are due to writes on one thread and reads on another; immutable objects don't have writes.
Immutable objects can be taken apart and re-used. For example, if you have an immutable binary tree then you can use its left and right subtrees as subtrees of a different tree without worrying about it. In a mutable structure you typically end up making copies of data to re-use it because you don't want changes to one logical object affecting another. This can save lots of time and memory.
Reasons to make structs immutable
There are lots of reasons to make structs immutable. Here's just one.
Structs are copied by value, not by reference. It is easy to accidentally treat a struct as being copied by reference. For example:
Now you want to refactor some of that code into a helper method:
WRONG! That should be (ref S s) -- if you don't do that then the mutation will happen on a copy of s. If you don't allow mutations in the first place then all these sorts of problems go away.
Reasons to make strings immutable
Remember my first point about facts about immutable structures staying facts?
Suppose string were mutable:
What if the hostile caller mutates filename after the security check and before the file is opened? The code just opened a file that they might not have permission to!
Again, mutable data is hard to reason about. You want the fact "this caller is authorized to see the file described by this string" to be true forever, not until a mutation happens. With mutable strings, to write secure code we'd constantly have to be making copies of data that we know do not change.
Does the type logically represent something that is an "eternal" value? The number 12 is the number 12; it doesn't change. Integers should be immutable. The point (10, 30) is the point (10, 30); it doesn't change. Points should be immutable. The string "abc" is the string "abc"; it doesn't change. Strings should be immutable. The list (10, 20, 30) doesn't change. And so on.
Sometimes the type represents things that do change. Mary Smith's last name is Smith, but tomorrow she might be Mary Jones. Or Miss Smith today might be Doctor Smith tomorrow. The alien has fifty health points now but has ten after being hit by the laser beam. Some things are best represented as mutations.
Not as such. As I mentioned before though, one of the nice things about immutable values is that something you can re-use parts of them without making copies. So in that sense, memory allocation can be very different.
结构不一定是不可变的,但可变结构是邪恶的。
创建可变结构可能会导致应用程序中出现各种奇怪的行为,因此,它们被认为是一个非常糟糕的主意(因为它们看起来像引用类型,但实际上是值类型,并且每当您传递时都会被复制)他们周围)。
另一方面,字符串是不可变的。这使得它们本质上是线程安全的,并且允许通过字符串驻留进行优化。如果您需要即时构建复杂的字符串,可以使用
StringBuilder
。Structs are not necessarily immutable, but mutable structs are evil.
Creating mutable structs can lead to all kinds of strange behavior in your application and, therefore, they are considered a very bad idea (stemming from the fact that they look like a reference type but are actually a value type and will be copied whenever you pass them around).
Strings, on the other hand, are immutable. This makes them inherently thread-safe as well as allowing for optimizations via string interning. If you need to construct a complicated string on the fly, you can use
StringBuilder
.结构类型不是不可变的。是的,字符串是。使您自己的类型不可变很容易,只需不提供默认构造函数,将所有字段设为私有,并且不定义更改字段值的方法或属性。让一个应该改变对象的方法返回一个新对象。有一个内存管理的角度,你往往会创建大量的副本和垃圾。
A struct type is not immutable. Yes, strings are. Making your own type immutable is easy, simply don't provide a default constructor, make all fields private and define no methods or properties that change a field value. Have a method that should mutate the object return a new object instead. There is a memory management angle, you tend to create a lot of copies and garbage.
可变性和不可变性的概念应用于结构和类时具有不同的含义。可变类的一个关键方面(通常是关键弱点)是
Foo
是否有一个List
类型的字段Bar
,其中包含对包含 (1,2,3) 的列表的引用,引用同一列表的其他代码可以修改它,这样Bar
保存对包含 (4,5, 6),即使其他代码无法访问Bar
。相比之下,如果Foo
具有System.Drawing.Point
类型的字段Biz
,则任何内容都可以修改任何方面的唯一方法Biz
将拥有该字段的写入权限。结构体的字段(公共和私有)可以由任何可以改变存储该结构体的存储位置的代码来改变,并且不能由任何不能改变其存储位置的代码来改变。如果封装在结构体中的所有信息都保存在其字段中,则这样的结构体可以有效地将不可变类型的控制与可变类型的便利性结合起来,除非该结构体的编码方式消除了这种便利性(不幸的是,一些微软程序员推荐了这个习惯)。
结构的“问题”是,当在只读上下文(或不可变位置)中的结构上调用方法(包括属性实现)时,系统会复制该结构,在临时副本上执行该方法,然后默默地执行该方法。丢弃结果。这种行为导致程序员提出了一个不幸的想法,即避免变异方法出现问题的方法是让许多结构不允许分段更新,而通过简单地用公开字段替换属性可以更好地避免问题。 >。
顺便说一句,有些人抱怨当类属性返回一个方便可变的结构时,对该结构的更改不会影响它所来自的类。我认为这是一件好事——返回的项是一个结构这一事实使得行为变得清晰(特别是如果它是一个暴露字段结构)。将使用
Drawing.Matrix
上的假设结构和属性的片段与使用 Microsoft 实现的该类上的实际属性的片段进行比较:查看实际的 Microsoft 属性,是否有任何方法可以判断是否写入到
myArray[4]
会影响myMatrix
吗?即使查看页面 http://msdn。 microsoft.com/en-us/library/system.drawing.drawing2d.matrix.elements.aspx 有什么方法可以告诉吗?如果该属性是使用基于结构的等效项编写的,则不会产生混淆;返回该结构的属性将返回最多也不小于六个数字的当前值。更改 myTransform.dx 只不过是写入一个不附加任何其他内容的浮点变量。任何不喜欢更改myTransform.dx
不会影响myMatrix
的人都应该同样恼火,因为编写myArray[4]
不会影响也不影响myMatrix
,除了myMatrix
和myTransform
的独立性是显而易见的,而myMatrix
的独立性而myArray
则不然。The concepts of mutability and immutability have different meanings when applied to structs and classes. A key aspect (oftentimes, the key weakness) of mutable classes is if
Foo
has a fieldBar
of typeList<Integer>
, which holds a reference to a list containing (1,2,3), other code which has a reference to that same list could modify it, such thatBar
holds a reference to a list containing (4,5,6), even if that other code has no access whatsoever toBar
. By contrast, ifFoo
had a fieldBiz
of typeSystem.Drawing.Point
, the only way anything could modify any aspect ofBiz
would be to have write access to that field.The fields (public and private) of a struct can be mutated by any code which can mutate the storage location in which the struct is stored, and cannot be mutated by any code which cannot mutate the storage location where it is stored. If all of the information encapsulated within a struct is held in its fields, such a struct can effectively combine the control of an immutable type with the convenience of a mutable type, unless the struct is coded in such a way as to remove such convenience (a habit which, unfortunately, some Microsoft programmers recommend).
The "problem" with structs is that when a method (including a property implementation) is invoked on a struct in a read-only context (or immutable location), the system copies the struct, performs the method on the temporary copy, and silently discards the result. This behavior has led programmers to put forth the unfortunate notion that the way to avoid problems with mutating methods is to have many structs disallow piecewise updates, when the problems could have been better avoided by simply replacing properties with exposed fields.
Incidentally, some people complain that when a class property returns a conveniently-mutable struct, changes to the struct don't affect the class from which it came. I would posit that's a good thing--the fact that the returned item is a struct makes the behavior clear (especially if it's an exposed-field struct). Compare a snippet using a hypothetical struct and property on
Drawing.Matrix
with one using an actual property on that class as implemented by Microsoft:Looking at the actual Microsoft property, is there any way to tell whether the write to
myArray[4]
will affectmyMatrix
? Even looking at the page http://msdn.microsoft.com/en-us/library/system.drawing.drawing2d.matrix.elements.aspx is there any way to tell? If the property had been written using the struct-based equivalent, there would be no confusion; the property that returns the struct would return nothing more nor less than the present value of six numbers. ChangingmyTransform.dx
would be nothing more nor less than a write to a floating-point variable which was unattached to anything else. Anyone who doesn't like the fact that changingmyTransform.dx
doesn't affectmyMatrix
should be equally annoyed that writingmyArray[4]
doesn't affectmyMatrix
either, except that the independence ofmyMatrix
andmyTransform
is apparent, while the independence ofmyMatrix
andmyArray
is not.结构体可以是可变的,但这是一个坏主意,因为它们具有复制语义。如果您对结构进行更改,您实际上可能正在修改一个副本。准确跟踪已更改的内容非常棘手。
可变结构会产生错误。
Structs can be mutable, but it's a bad idea because they have copy-semantics. If you make a change to a struct, you might actually be modifying a copy. Keeping track of exactly what has been changed is very tricky.
Mutable structs breed mistakes.