为什么可变结构是“邪恶的”?
在关于 SO 的讨论之后,我已经多次读到可变结构是“邪恶的”的评论(就像这个问题)。
C# 中的可变性和结构的实际问题是什么?
Following the discussions here on SO I already read several times the remark that mutable structs are “evil” (like in the answer to this question).
What's the actual problem with mutability and structs in C#?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(16)
结构是值类型,这意味着它们在传递时会被复制。
因此,如果您更改副本,您只会更改该副本,而不是原始副本,也不会更改可能存在的任何其他副本。
如果您的结构是不可变的,那么通过值传递产生的所有自动副本将是相同的。
如果您想更改它,您必须有意识地使用修改后的数据创建结构的新实例。 (不是副本)
Structs are value types which means they are copied when they are passed around.
So if you change a copy you are changing only that copy, not the original and not any other copies which might be around.
If your struct is immutable then all automatic copies resulting from being passed by value will be the same.
If you want to change it you have to consciously do it by creating a new instance of the struct with the modified data. (not a copy)
从哪里开始 ;-p
Eric Lippert 的博客总是适合引用:
首先,您往往很容易丢失更改......例如,从列表中取出内容:
这改变了什么? 没有什么用处...
与属性相同:
强迫你做:
不太重要的是,存在大小问题; 可变对象往往具有多个属性; 但是,如果您有一个包含两个
int
、一个string
、一个DateTime
和一个bool
的结构,您可以很快就会消耗大量内存。 对于一个类,多个调用者可以共享对同一实例的引用(引用很小)。Where to start ;-p
Eric Lippert's blog is always good for a quote:
First, you tend to lose changes quite easily... for example, getting things out of a list:
what did that change? Nothing useful...
The same with properties:
forcing you to do:
less critically, there is a size issue; mutable objects tend to have multiple properties; yet if you have a struct with two
int
s, astring
, aDateTime
and abool
, you can very quickly burn through a lot of memory. With a class, multiple callers can share a reference to the same instance (references are small).我不会说“邪恶”,但可变性通常是程序员过度渴望提供最大功能的标志。 实际上,这通常是不需要的,这反过来又使界面更小,更易于使用并且更难使用错误(=更健壮)。
其中一个例子是竞争条件下的读/写和写/写冲突。 这些根本不可能发生在不可变结构中,因为写入不是有效的操作。
此外,我声称实际上几乎从来不需要可变性,程序员只是认为它可能在未来。 例如,更改日期根本没有意义。 相反,根据旧日期创建一个新日期。 这是一个廉价的操作,因此性能不是考虑因素。
I wouldn't say evil but mutability is often a sign of overeagerness on the part of the programmer to provide a maximum of functionality. In reality, this is often not needed and that, in turn, makes the interface smaller, easier to use and harder to use wrong (= more robust).
One example of this is read/write and write/write conflicts in race conditions. These simply can't occur in immutable structures, since a write is not a valid operation.
Also, I claim that mutability is almost never actually needed, the programmer just thinks that it might be in the future. For example, it simply doesn't make sense to change a date. Rather, create a new date based off the old one. This is a cheap operation, so performance is not a consideration.
可变结构并不是邪恶的。
它们在高性能环境中是绝对必要的。 例如,当缓存行和/或垃圾收集成为瓶颈时。
我不会将这些完全有效的用例中使用不可变结构称为“邪恶”。
我可以看出,C# 的语法无助于区分值类型或引用类型的成员的访问,因此我完全赞成更喜欢不可变结构,它强制不可变性,而不是可变的结构。
然而,我建议不要简单地将不可变结构标记为“邪恶”,而是拥抱这种语言并提倡更有帮助和建设性的经验法则。
例如:“结构是值类型,默认情况下会复制。如果您不想复制它们,则需要引用” 或
“首先尝试使用只读结构”。
Mutable structs are not evil.
They are absolutely necessary in high performance circumstances. For example when cache lines and or garbage collection become a bottleneck.
I would not call the use of a immutable struct in these perfectly valid use-cases "evil".
I can see the point that C#'s syntax does not help to distinguish the access of a member of a value type or of a reference type, so I am all for preferring immutable structs, that enforce immutability, over mutable structs.
However, instead of simply labelling immutable structs as "evil", I would advise to embrace the language and advocate more helpful and constructive rule of thumbs.
For example: "structs are value types, which are copied by default. you need a reference if you don't want to copy them" or
"try to work with readonly structs first".
具有公共可变字段或属性的结构体并不是邪恶的。
改变“this”的结构体方法(与属性设置器不同)有些邪恶,只是因为 .net 没有提供区分它们的方法没有的方法。 即使在只读结构上,也应该可以调用不改变“this”的结构方法,而无需进行防御性复制。 改变“this”的方法根本不应该在只读结构上调用。 由于 .net 不想禁止在只读结构上调用不修改“this”的结构方法,但又不想允许只读结构发生变异,因此它会防御性地以只读方式复制结构。只有上下文,可以说是两全其美。
然而,尽管在只读上下文中处理自变异方法存在问题,但可变结构通常提供远远优于可变类类型的语义。 考虑以下三个方法签名:
对于每个方法,回答以下问题:
答案:
Method1 无法修改 foo,并且永远不会获取引用。 Method2 获取对 foo 的短暂引用,它可以使用该引用以任何顺序修改 foo 的字段任意次数,直到它返回,但它无法保留该引用。 在 Method2 返回之前,除非它使用不安全的代码,否则可能由其“foo”引用构成的任何和所有副本都将消失。 与 Method2 不同,Method3 获取对 foo 的混杂共享引用,并且不知道它会用它做什么。 它可能根本不会改变 foo,它可能会改变 foo 然后返回,或者它可能会将 foo 的引用提供给另一个线程,该线程可能会在未来的某个任意时间以某种任意方式改变它。 限制 Method3 对传递给它的可变类对象执行的操作的唯一方法是将可变对象封装到只读包装器中,这是丑陋且麻烦的。
结构数组提供了美妙的语义。 给定 Rectangle 类型的 RectArray[500],很明显如何将元素 123 复制到元素 456,然后一段时间后将元素 123 的宽度设置为 555,而不干扰元素 456。“ RectArray[432] = RectArray[321 ]; 矩形数组[123].宽度= 555;”。 知道 Rectangle 是一个具有名为 Width 的整数字段的结构体,就可以了解上述语句所需的所有信息。
现在假设 RectClass 是一个与 Rectangle 具有相同字段的类,并且想要对 RectClass 类型的 RectClassArray[500] 执行相同的操作。 也许该数组应该保存 500 个对可变 RectClass 对象的预先初始化的不可变引用。 在这种情况下,正确的代码类似于“RectClassArray[321].SetBounds(RectClassArray[456]); ...; RectClassArray[321].X = 555;”。 也许数组被假设保存不会改变的实例,所以正确的代码更像是“ RectClassArray [321] = RectClassArray [456]; ...; RectClassArray [321] = New RectClass(RectClassArray [321 ]); 矩形类数组[321].X = 555;" 要知道应该做什么,就必须了解有关 RectClass 的更多信息(例如,它是否支持复制构造函数、复制源方法等)以及数组的预期用途。 远没有使用结构那么干净。
可以肯定的是,不幸的是,除了数组之外,没有任何容器类可以提供结构体数组的干净语义。 如果希望使用字符串等对集合进行索引,最好的方法可能是提供一种通用的“ActOnItem”方法,该方法将接受索引字符串、通用参数和将传递的委托通过引用通用参数和集合项。 这将允许与结构数组几乎相同的语义,但除非可以说服 vb.net 和 C# 人员提供良好的语法,否则即使性能合理(传递通用参数将允许使用静态委托并避免创建任何临时类实例的需要)。
就我个人而言,我对埃里克·利珀特等人的仇恨感到恼火。 关于可变值类型的喷涌。 它们提供比到处使用的混杂引用类型更清晰的语义。 尽管 .net 对值类型的支持存在一些限制,但在许多情况下,可变值类型比任何其他类型的实体都更适合。
Structs with public mutable fields or properties are not evil.
Struct methods (as distinct from property setters) which mutate "this" are somewhat evil, only because .net doesn't provide a means of distinguishing them from methods which do not. Struct methods that do not mutate "this" should be invokable even on read-only structs without any need for defensive copying. Methods which do mutate "this" should not be invokable at all on read-only structs. Since .net doesn't want to forbid struct methods that don't modify "this" from being invoked on read-only structs, but doesn't want to allow read-only structs to be mutated, it defensively copies structs in read-only contexts, arguably getting the worst of both worlds.
Despite the problems with the handling of self-mutating methods in read-only contexts, however, mutable structs often offer semantics far superior to mutable class types. Consider the following three method signatures:
For each method, answer the following questions:
Answers:
Method1 can't modify foo, and never gets a reference. Method2 gets a short-lived reference to foo, which it can use modify the fields of foo any number of times, in any order, until it returns, but it can't persist that reference. Before Method2 returns, unless it uses unsafe code, any and all copies that might have been made of its 'foo' reference will have disappeared. Method3, unlike Method2, gets a promiscuously-sharable reference to foo, and there's no telling what it might do with it. It might not change foo at all, it might change foo and then return, or it might give a reference to foo to another thread which might mutate it in some arbitrary way at some arbitrary future time. The only way to limit what Method3 might do to a mutable class object passed into it would be to encapsulate the mutable object into a read-only wrapper, which is ugly and cumbersome.
Arrays of structures offer wonderful semantics. Given RectArray[500] of type Rectangle, it's clear and obvious how to e.g. copy element 123 to element 456 and then some time later set the width of element 123 to 555, without disturbing element 456. "RectArray[432] = RectArray[321]; ...; RectArray[123].Width = 555;". Knowing that Rectangle is a struct with an integer field called Width will tell one all one needs to know about the above statements.
Now suppose RectClass was a class with the same fields as Rectangle and one wanted to do the same operations on a RectClassArray[500] of type RectClass. Perhaps the array is supposed to hold 500 pre-initialized immutable references to mutable RectClass objects. in that case, the proper code would be something like "RectClassArray[321].SetBounds(RectClassArray[456]); ...; RectClassArray[321].X = 555;". Perhaps the array is assumed to hold instances that aren't going to change, so the proper code would be more like "RectClassArray[321] = RectClassArray[456]; ...; RectClassArray[321] = New RectClass(RectClassArray[321]); RectClassArray[321].X = 555;" To know what one is supposed to do, one would have to know a lot more both about RectClass (e.g. does it support a copy constructor, a copy-from method, etc.) and the intended usage of the array. Nowhere near as clean as using a struct.
To be sure, there is unfortunately no nice way for any container class other than an array to offer the clean semantics of a struct array. The best one could do, if one wanted a collection to be indexed with e.g. a string, would probably be to offer a generic "ActOnItem" method which would accept a string for the index, a generic parameter, and a delegate which would be passed by reference both the generic parameter and the collection item. That would allow nearly the same semantics as struct arrays, but unless the vb.net and C# people can be pursuaded to offer a nice syntax, the code is going to be clunky-looking even if it is reasonably performance (passing a generic parameter would allow for use of a static delegate and would avoid any need to create any temporary class instances).
Personally, I'm peeved at the hatred Eric Lippert et al. spew regarding mutable value types. They offer much cleaner semantics than the promiscuous reference types that are used all over the place. Despite some of the limitations with .net's support for value types, there are many cases where mutable value types are a better fit than any other kind of entity.
从程序员的角度来看,还有一些其他极端情况可能会导致不可预测的行为。
不可变值类型和只读字段
可变值类型和数组
假设我们有一个
Mutable
结构体数组,并且我们正在为该数组的第一个元素调用IncrementI
方法。 您期望这次通话有什么行为? 它应该更改数组的值还是仅更改副本?因此,只要您和团队的其他成员清楚地了解您在做什么,可变结构就不是邪恶的。 但是有太多的极端情况,当程序行为与预期不同时,这可能会导致难以产生和难以理解的微妙错误。
There are a couple other corner cases that could lead to unpredictable behavior from the programmer's point of view.
Immutable value types and readonly fields
Mutable value types and array
Suppose we have an array of our
Mutable
struct and we're calling theIncrementI
method for the first element of that array. What behavior are you expecting from this call? Should it change the array's value or only a copy?So, mutable structs are not evil as long as you and the rest of the team clearly understand what you are doing. But there are too many corner cases when the program behavior would be different from what's expected, that could lead to subtle hard to produce and hard to understand errors.
值类型基本上代表不可变的概念。 Fx,拥有一个整数、向量等数学值然后能够对其进行修改是没有意义的。 这就像重新定义一个值的含义。 分配另一个唯一值比更改值类型更有意义。 考虑一下这样的事实:值类型是通过比较其属性的所有值来进行比较的。 关键是,如果属性相同,那么它就是该值的相同通用表示。
正如康拉德提到的那样,更改日期也没有意义,因为该值表示唯一的时间点,而不是具有任何状态或上下文依赖性的时间对象的实例。
希望这对您有意义。 当然,它更多的是关于您尝试用值类型捕获的概念,而不是实际细节。
Value types basically represents immutable concepts. Fx, it makes no sense to have a mathematical value such as an integer, vector etc. and then be able to modify it. That would be like redefining the meaning of a value. Instead of changing a value type, it makes more sense to assign another unique value. Think about the fact that value types are compared by comparing all the values of its properties. The point is that if the properties are the same then it is the same universal representation of that value.
As Konrad mentions it doesn't make sense to change a date either, as the value represents that unique point in time and not an instance of a time object which has any state or context-dependency.
Hopes this makes any sense to you. It is more about the concept you try to capture with value types than practical details, to be sure.
如果您曾经使用 C/C++ 等语言进行过编程,那么结构体就可以用作可变的。 只需将它们与 ref, around 一起传递,就不会出错。 我发现的唯一问题是 C# 编译器的限制,在某些情况下,我无法强制愚蠢的事情使用对结构的引用,而不是 Copy(就像当结构是 C# 类的一部分时) )。
因此,可变结构并不是邪恶的,而是 C# 使它们变得邪恶。 我一直在 C++ 中使用可变结构,它们非常方便和直观。 相比之下,C# 让我完全放弃将结构体作为类的成员,因为它们处理对象的方式。 他们的便利让我们付出了代价。
If you have ever programmed in a language like C/C++, structs are fine to use as mutable. Just pass them with ref, around and there is nothing that can go wrong. The only problem I find are the restrictions of the C# compiler and that, in some cases, I am unable to force the stupid thing to use a reference to the struct, instead of a Copy(like when a struct is part of a C# class).
So, mutable structs are not evil, C# has made them evil. I use mutable structs in C++ all the time and they are very convenient and intuitive. In contrast, C# has made me to completely abandon structs as members of classes because of the way they handle objects. Their convenience has cost us ours.
想象一下您有一个包含 1,000,000 个结构的数组。 每个结构代表一个权益,包含 bid_price、offer_price(可能是小数)等内容,这是由 C#/VB 创建的。
想象一下,该数组是在非托管堆中分配的内存块中创建的,以便其他一些本机代码线程能够同时访问该数组(可能是一些执行数学运算的高性能代码)。
想象一下 C#/VB 代码正在侦听价格变化的市场反馈,该代码可能必须访问数组的某些元素(无论哪种证券),然后修改某些价格字段。
想象一下每秒执行数万甚至数十万次。
好吧,让我们面对事实,在这种情况下,我们确实希望这些结构是可变的,它们需要是可变的,因为它们正在被其他一些本机代码共享,所以创建副本不会有帮助; 它们需要如此,因为以这样的速率复制一些 120 字节结构的副本是疯狂的,特别是当更新实际上可能只影响一两个字节时。
雨果
Imagine you have an array of 1,000,000 structs. Each struct representing an equity with stuff like bid_price, offer_price (perhaps decimals) and so on, this is created by C#/VB.
Imagine that array is created in a block of memory allocated in the unmanaged heap so that some other native code thread is able to concurrently access the array (perhaps some high-perf code doing math).
Imagine the C#/VB code is listening to a market feed of price changes, that code may have to access some element of the array (for whichever security) and then modify some price field(s).
Imagine this is being done tens or even hundreds of thousands of times per second.
Well lets face facts, in this case we really do want these structs to be mutable, they need to be because they are being shared by some other native code so creating copies isn't gonna help; they need to be because making a copy of some 120 byte struct at these rates is lunacy, especially when an update may actually impact just a byte or two.
Hugo
如果您坚持了解结构体的用途(在 C#、Visual Basic 6、Pascal/Delphi、C++ 结构体类型(或类)中,当它们不用作指针时),您会发现结构体只不过是一个 复合变量。 这意味着:您将把它们视为一组打包的变量,并使用通用名称(您从中引用成员的记录变量)。
我知道这会让很多习惯 OOP 的人感到困惑,但是如果使用得当,这并不足以说明这些东西本质上是邪恶的。 有些结构按照其预期是不可变的(Python 的
namedtuple
就是这种情况),但这是需要考虑的另一种范例。是的:结构体涉及大量内存,但通过执行以下操作不会精确地获得更多内存:
与: 相比:
内存消耗至少是相同的,或者在不可变的情况下甚至更多(尽管这种情况是暂时的,对于当前堆栈,具体取决于语言)。
但最后,结构只是结构,而不是对象。 在 POO 中,对象的主要属性是它们的身份,大多数时候它不超过其内存地址。 struct 代表数据结构(不是一个适当的对象,因此它们无论如何都没有标识),并且数据可以被修改。 在其他语言中,record(而不是 struct,如 Pascal 的情况)是这个词并且具有相同的用途:只是一个数据记录变量,旨在读取从文件中修改,然后转储到文件中(这是主要用途,在许多语言中,您甚至可以在记录中定义数据对齐方式,但对于正确调用的对象来说不一定是这种情况)。
想要一个好的例子吗? 结构体用于轻松读取文件。 Python 有这个库,因为它是面向对象的并且不支持结构,所以它必须以另一种方式实现它,这有点难看。 实现结构的语言具有该功能......内置。 尝试用 Pascal 或 C 等语言读取具有适当结构的位图标头。这会很容易(如果结构正确构建和对齐;在 Pascal 中,您不会使用基于记录的访问,而是使用读取任意二进制数据的函数)。 因此,对于文件和直接(本地)内存访问,结构比对象更好。 就今天而言,我们已经习惯了 JSON 和 XML,因此我们忘记了二进制文件的使用(以及副作用,忘记了结构的使用)。 但是,是的:它们存在,并且有目的。
他们并不邪恶。 只需将它们用于正确的目的即可。
如果你用锤子来思考,你就会想把螺丝钉当作钉子,发现螺丝钉更难扎进墙里,那就是螺丝钉的错,是螺丝钉的祸害。
If you stick to what structs are intended for (in C#, Visual Basic 6, Pascal/Delphi, C++ struct type (or classes) when they are not used as pointers), you will find that a structure is not more than a compound variable. This means: you will treat them as a packed set of variables, under a common name (a record variable you reference members from).
I know that would confuse a lot of people deeply used to OOP, but that's not enough reason to say such things are inherently evil, if used correctly. Some structures are inmutable as they intend (this is the case of Python's
namedtuple
), but it is another paradigm to consider.Yes: structs involve a lot of memory, but it will not be precisely more memory by doing:
compared to:
The memory consumption will be at least the same, or even more in the inmutable case (although that case would be temporary, for the current stack, depending on the language).
But, finally, structures are structures, not objects. In POO, the main property of an object is their identity, which most of the times is not more than its memory address. Struct stands for data structure (not a proper object, and so they don't have identity anyhow), and data can be modified. In other languages, record (instead of struct, as is the case for Pascal) is the word and holds the same purpose: just a data record variable, intended to be read from files, modified, and dumped into files (that is the main use and, in many languages, you can even define data alignment in the record, while that's not necessarily the case for properly called Objects).
Want a good example? Structs are used to read files easily. Python has this library because, since it is object-oriented and has no support for structs, it had to implement it in another way, which is somewhat ugly. Languages implementing structs have that feature... built-in. Try reading a bitmap header with an appropriate struct in languages like Pascal or C. It will be easy (if the struct is properly built and aligned; in Pascal you would not use a record-based access but functions to read arbitrary binary data). So, for files and direct (local) memory access, structs are better than objects. As for today, we're used to JSON and XML, and so we forget the use of binary files (and as a side effect, the use of structs). But yes: they exist, and have a purpose.
They are not evil. Just use them for the right purpose.
If you think in terms of hammers, you will want to treat screws as nails, to find screws are harder to plunge in the wall, and it will be screws' fault, and they will be the evil ones.
当某些东西可以变异时,它就会获得一种认同感。
因为
Person
是可变的,所以考虑改变 Eric 的位置比克隆 Eric、移动克隆并销毁原始版本更自然。 两种操作都会成功更改 eric.position 的内容,但其中一种操作比另一种更直观。 同样,向 Eric 传递(作为参考)修改他的方法会更直观。 给一个方法克隆 Eric 几乎总是会令人惊讶。 任何想要改变Person
的人都必须记住请求对Person
的引用,否则他们会做错事。如果你使类型不可变,问题就会消失; 如果我无法修改
eric
,那么无论我收到eric
还是eric
的克隆,对我来说都没有区别。 更一般而言,如果类型的所有可观察状态都保存在以下成员中,则该类型可以安全地按值传递:如果满足这些条件,则可变值类型的行为类似于引用类型,因为浅拷贝仍然允许接收者修改原始数据。
不可变的
Person
的直观性取决于您想要做什么。 如果Person
只是表示关于一个人的一组数据,那么它并没有什么不直观的;Person
变量真正代表抽象值,而不是对象。 (在这种情况下,将其重命名为PersonData
可能更合适。)如果Person
实际上是在建模一个人本身,则不断创建和移动克隆的想法即使您避免了认为自己正在修改原始版本的陷阱,这也是愚蠢的。 在这种情况下,简单地将Person
设为引用类型(即类)可能会更自然。当然,正如函数式编程告诉我们的那样,将所有东西都设为是有好处的。 /em> 不可变(没有人可以秘密保留对
eric
的引用并改变他),但由于这在 OOP 中不是惯用的,所以对于其他使用你的代码的人来说仍然是不直观的。When something can be mutated, it gains a sense of identity.
Because
Person
is mutable, it's more natural to think about changing Eric's position than cloning Eric, moving the clone, and destroying the original. Both operations would succeed in changing the contents oferic.position
, but one is more intuitive than the other. Likewise, it's more intuitive to pass Eric around (as a reference) for methods to modify him. Giving a method a clone of Eric is almost always going to be surprising. Anyone wanting to mutatePerson
must remember to ask for a reference toPerson
or they'll be doing the wrong thing.If you make the type immutable, the problem goes away; if I can't modify
eric
, it makes no difference to me whether I receiveeric
or a clone oferic
. More generally, a type is safe to pass by value if all of its observable state is held in members that are either:If those conditions are met then a mutable value type behaves like a reference type because a shallow copy will still allow the receiver to modify the original data.
The intuitiveness of an immutable
Person
depends on what you're trying to do though. IfPerson
just represents a set of data about a person, there's nothing unintuitive about it;Person
variables truly represent abstract values, not objects. (In that case, it'd probably be more appropriate to rename it toPersonData
.) IfPerson
is actually modeling a person itself, the idea of constantly creating and moving clones is silly even if you've avoided the pitfall of thinking you're modifying the original. In that case it'd probably be more natural to simply makePerson
a reference type (that is, a class.)Granted, as functional programming has taught us there are benefits to making everything immutable (no one can secretly hold on to a reference to
eric
and mutate him), but since that's not idiomatic in OOP it's still going to be unintuitive to anyone else working with your code.它与结构没有任何关系(也与 C# 无关),但在 Java 中,当可变对象是哈希映射中的键时,您可能会遇到可变对象的问题。 如果将它们添加到映射后更改它们,它会更改其 哈希码,邪恶的事情可能会发生。
It doesn’t have anything to do with structs (and not with C#, either) but in Java you might get problems with mutable objects when they are e.g. keys in a hash map. If you change them after adding them to a map and it changes its hash code, evil things might happen.
可变数据有很多优点和缺点。 数百万美元的缺点是混叠。 如果在多个地方使用相同的值,并且其中一个地方更改了它,那么它看起来会神奇地更改为正在使用它的其他地方。 这与竞争条件相关,但并不完全相同。
有时,数百万美元的优势在于模块化。 可变状态可以让您对不需要了解变化的信息隐藏代码。
解释器的艺术详细讨论了这些权衡,并给出了一些例子。
There are many advantages and disadvantages to mutable data. The million-dollar disadvantage is aliasing. If the same value is being used in multiple places, and one of them changes it, then it will appear to have magically changed to the other places that are using it. This is related to, but not identical with, race conditions.
The million-dollar advantage is modularity, sometimes. Mutable state can allow you to hide changing information from code that doesn't need to know about it.
The Art of the Interpreter goes into these trade offs in some detail, and gives some examples.
就我个人而言,当我查看代码时,以下代码对我来说看起来相当笨重:
data.value.set ( data.value.get () + 1 ) ;
而不是简单的
data.value++ ; 或 data.value = data.value + 1 ;
当传递类并且您希望确保以受控方式修改值时,数据封装非常有用。 然而,当您拥有公共 set 和 get 函数时,它们的作用只不过是将值设置为传入的值,那么与简单地传递公共数据结构相比,这有何改进呢?
当我在类中创建私有结构时,我创建了该结构来将一组变量组织到一个组中。 我希望能够在类范围内修改该结构,而不是获取该结构的副本并创建新实例。
对我来说,这会阻止有效使用用于组织公共变量的结构,如果我想要访问控制,我会使用类。
Personally when I look at code the following looks pretty clunky to me:
data.value.set ( data.value.get () + 1 ) ;
rather than simply
data.value++ ; or data.value = data.value + 1 ;
Data encapsulation is useful when passing a class around and you want to ensure the value is modified in a controlled fashion. However when you have public set and get functions that do little more than set the value to what ever is passed in, how is this an improvement over simply passing a public data structure around?
When I create a private structure inside a class, I created that structure to organize a set of variables into one group. I want to be able to modify that structure within the class scope, not get copies of that structure and create new instances.
To me this prevents a valid use of structures being used to organize public variables, if I wanted access control I'd use a class.
埃里克·利珀特先生的例子有几个问题。 它旨在说明复制结构的这一点以及如果您不小心的话这可能会出现问题。 看看这个例子,我认为这是不良编程习惯的结果,而不是结构或类的真正问题。
结构应该只具有公共成员,并且不需要任何封装。 如果确实如此,那么它确实应该是一个类型/类。 您确实不需要两个构造来表达同一件事。
如果您有包含结构的类,则可以调用该类中的方法来更改成员结构。 这是我会做的一个良好的编程习惯。
正确的实现如下。
看起来这是编程习惯的问题,而不是结构本身的问题。 结构应该是可变的,这就是想法和意图。
更改的结果瞧,其行为符合预期:
1
2
3
按任意键继续 。 。 。
There are several issues with Mr. Eric Lippert's example. It is contrived to illustrate the point that structs are copied and how that could be a problem if you are not careful. Looking at the example I see it as a result of a bad programming habit and not really a problem with either struct or the class.
A struct is supposed to have only public members and should not require any encapsulation. If it does then it really should be a type/class. You really do not need two constructs to say the same thing.
If you have class enclosing a struct, you would call a method in the class to mutate the member struct. This is what I would do as a good programming habit.
A proper implementation would be as follows.
It looks like it is an issue with programming habit as opposed to an issue with struct itself. Structs are supposed to be mutable, that is the idea and intent.
The result of the changes voila behaves as expected:
1
2
3
Press any key to continue . . .
我不相信如果使用得当的话它们是邪恶的。 我不会将其放入我的生产代码中,但我会将其用于结构化单元测试模拟之类的东西,其中结构的生命周期相对较短。
使用 Eric 示例,也许您想要创建该 Eric 的第二个实例,但要进行调整,因为这是测试的本质(即复制,然后修改)。 如果我们只是将 Eric2 用于测试脚本的其余部分,那么 Eric2 的第一个实例会发生什么并不重要,除非您计划使用他作为测试比较。
这对于测试或修改浅层定义特定对象(结构点)的遗留代码非常有用,但是通过拥有不可变的结构,这可以防止它被烦人地使用。
I don't believe they're evil if used correctly. I wouldn't put it in my production code, but I would for something like structured unit testing mocks, where the lifespan of a struct is relatively small.
Using the Eric example, perhaps you want to create a second instance of that Eric, but make adjustments, as that's the nature of your test (ie duplication, then modifying). It doesn't matter what happens with the first instance of Eric if we're just using Eric2 for the remainder of the test script, unless you're planning on using him as a test comparison.
This would be mostly useful for testing or modifying legacy code that shallow defines a particular object (the point of structs), but by having an immutable struct, this prevents it's usage annoyingly.