简体   繁体   English

为什么可变结构是“邪恶的”?

[英]Why are mutable structs “evil”?

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 ).在 SO 上的讨论之后,我已经多次阅读了关于可变结构是“邪恶的”的评论(就像在这个问题的答案中一样)。

What's the actual problem with mutability and structs in C#? C# 中可变性和结构的实际问题是什么?

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) (不是副本)

Where to start ;-p从哪里开始;-p

Eric Lippert's blog is always good for a quote: Eric Lippert 的博客总是很适合引用:

This is yet another reason why mutable value types are evil.这是可变值类型是邪恶的另一个原因。 Try to always make value types immutable.尝试始终使值类型不可变。

First, you tend to lose changes quite easily... for example, getting things out of a list:首先,您往往很容易丢失更改……例如,从列表中取出内容:

Foo foo = list[0];
foo.Name = "abc";

what did that change?那有什么改变? Nothing useful...没什么用...

The same with properties:与属性相同:

myObj.SomeProperty.Size = 22; // the compiler spots this one

forcing you to do:强迫你做:

Bar bar = myObj.SomeProperty;
bar.Size = 22;
myObj.SomeProperty = bar;

less critically, there is a size issue;不太重要的是,存在尺寸问题; mutable objects tend to have multiple properties;可变对象往往具有多个属性; yet if you have a struct with two int s, a string , a DateTime and a bool , you can very quickly burn through a lot of memory.但是,如果您有一个包含两个int 、一个string 、一个DateTime和一个bool ,您会很快消耗大量内存。 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.这是一个廉价的操作,所以性能不是一个考虑因素。

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.我可以看到,C#的语法不利于区分值类型或引用类型的成员的接入点,所以我所有喜欢一成不变的结构,即强制执行不变性,在可变的结构。

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" .例如: “结构是值类型,默认情况下被复制。如果你不想复制它们,你需要一个引用”“尝试首先使用只读结构”

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.改变“this”的结构方法(与属性设置器不同)有点邪恶,只是因为.net没有提供将它们与没有的方法区分开来的方法。 Struct methods that do not mutate "this" should be invokable even on read-only structs without any need for defensive copying.不改变“this”的结构方法即使在只读结构上也应该是可调用的,而无需任何防御性复制。 Methods which do mutate "this" should not be invokable at all on read-only structs.改变“this”的方法根本不应该在只读结构上调用。 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.由于 .net 不想禁止不修改“this”的结构方法在只读结构上被调用,但不想允许只读结构发生变异,因此它防御性地复制只读结构只有上下文,可以说是两全其美。

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:考虑以下三个方法签名:

struct PointyStruct {public int x,y,z;};
class PointyClass {public int x,y,z;};

void Method1(PointyStruct foo);
void Method2(ref PointyStruct foo);
void Method3(PointyClass foo);

For each method, answer the following questions:对于每种方法,请回答以下问题:

  1. Assuming the method doesn't use any "unsafe" code, might it modify foo?假设该方法不使用任何“不安全”代码,它是否会修改 foo?
  2. If no outside references to 'foo' exist before the method is called, could an outside reference exist after?如果在调用该方法之前不存在对 'foo' 的外部引用,那么在调用方法之后是否会存在外部引用?

Answers:答案:

Question 1:问题 1:
Method1() : no (clear intent) Method1() :否(意图明确)
Method2() : yes (clear intent) Method2() : 是(意图明确)
Method3() : yes (uncertain intent) Method3() : 是(不确定意图)
Question 2:问题2:
Method1() : no Method1() : 没有
Method2() : no (unless unsafe) Method2() :否(除非不安全)
Method3() : yes Method3() : 是

Method1 can't modify foo, and never gets a reference. Method1 不能修改 foo,并且永远不会得到引用。 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. Method2 获得对 foo 的短期引用,它可以使用它以任何顺序修改 foo 的字段任意次数,直到它返回,但它不能保留该引用。 Before Method2 returns, unless it uses unsafe code, any and all copies that might have been made of its 'foo' reference will have disappeared.在 Method2 返回之前,除非它使用不安全的代码,否则任何和所有可能由其 'foo' 引用制成的副本都将消失。 Method3, unlike Method2, gets a promiscuously-sharable reference to foo, and there's no telling what it might do with it.与 Method2 不同的是,Method3 获得了一个对 foo 的可混杂共享的引用,并且不知道它可以用它做什么。 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.它可能根本不会改变 foo,它可能会改变 foo 然后返回,或者它可能会将 foo 的引用提供给另一个线程,该线程可能会在未来某个任意时间以某种任意方式改变它。 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.限制 Method3 可能对传递给它的可变类对象执行的操作的唯一方法是将可变对象封装到只读包装器中,这既丑陋又麻烦。

Arrays of structures offer wonderful semantics.结构数组提供了美妙的语义。 Given RectArray[500] of type Rectangle, it's clear and obvious how to eg 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;".给定 Rectangle 类型的 RectArray[500],很明显如何例如将元素 123 复制到元素 456,然后在一段时间后将元素 123 的宽度设置为 555,而不会干扰元素 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.知道 Rectangle 是一个结构体,它有一个名为 Width 的整数字段,这将告诉人们所有需要了解的上述语句。

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.现在假设 RectClass 是一个与 Rectangle 具有相同字段的类,并且想要对 RectClass 类型的 RectClassArray[500] 执行相同的操作。 Perhaps the array is supposed to hold 500 pre-initialized immutable references to mutable RectClass objects.也许该数组应该保存 500 个对可变 RectClass 对象的预初始化的不可变引用。 in that case, the proper code would be something like "RectClassArray[321].SetBounds(RectClassArray[456]); ...; RectClassArray[321].X = 555;".在这种情况下,正确的代码将类似于“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;"也许假设数组包含不会改变的实例,所以正确的代码更像是“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 (eg does it support a copy constructor, a copy-from method, etc.) and the intended usage of the array.要知道一个人应该做什么,就必须了解更多关于 RectClass(例如,它是否支持复制构造函数、复制自方法等)和数组的预期用途。 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 eg 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.最好的方法是,如果想要使用例如字符串对集合进行索引,则可能是提供一个通用的“ActOnItem”方法,该方法将接受一个字符串作为索引、一个通用参数和一个将被传递的委托通过引用泛型参数和集合项。 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).这将允许与 struct 数组几乎相同的语义,但除非 vb.net 和 C# 人员可以提供良好的语法,否则即使代码具有合理的性能(传递泛型参数会允许使用静态委托,并且无需创建任何临时类实例)。

Personally, I'm peeved at the hatred Eric Lippert et al.就个人而言,我对 Eric Lippert 等人的仇恨感到恼火。 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.尽管 .net 对值类型的支持存在一些限制,但在许多情况下,可变值类型比任何其他类型的实体都更适合。

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不可变值类型和只读字段

    // Simple mutable structure. 
    // Method IncrementI mutates current state.
    struct Mutable
    {
        public Mutable(int i) : this() 
        {
            I = i;
        }

        public void IncrementI() { I++; }

        public int I { get; private set; }
    }

    // Simple class that contains Mutable structure
    // as readonly field
    class SomeClass 
    {
        public readonly Mutable mutable = new Mutable(5);
    }

    // Simple class that contains Mutable structure
    // as ordinary (non-readonly) field
    class AnotherClass 
    {
        public Mutable mutable = new Mutable(5);
    }

    class Program
    {
        void Main()
        {
            // Case 1. Mutable readonly field
            var someClass = new SomeClass();
            someClass.mutable.IncrementI();
            // still 5, not 6, because SomeClass.mutable field is readonly
            // and compiler creates temporary copy every time when you trying to
            // access this field
            Console.WriteLine(someClass.mutable.I);

            // Case 2. Mutable ordinary field
            var anotherClass = new AnotherClass();
            anotherClass.mutable.IncrementI();

            // Prints 6, because AnotherClass.mutable field is not readonly
            Console.WriteLine(anotherClass.mutable.I);
        }
    }

Mutable value types and array可变值类型和数组

Suppose we have an array of our Mutable struct and we're calling the IncrementI method for the first element of that array.假设我们有一个Mutable结构数组,并且我们正在为该数组的第一个元素调用IncrementI方法。 What behavior are you expecting from this call?您期望此调用有什么行为? Should it change the array's value or only a copy?它应该改变数组的值还是只改变一个副本?

    Mutable[] arrayOfMutables = new Mutable[1];
    arrayOfMutables[0] = new Mutable(5);

    // Now we actually accessing reference to the first element
    // without making any additional copy
    arrayOfMutables[0].IncrementI();

    // Prints 6!!
    Console.WriteLine(arrayOfMutables[0].I);

    // Every array implements IList<T> interface
    IList<Mutable> listOfMutables = arrayOfMutables;

    // But accessing values through this interface lead
    // to different behavior: IList indexer returns a copy
    // instead of an managed reference
    listOfMutables[0].IncrementI(); // Should change I to 7

    // Nope! we still have 6, because previous line of code
    // mutate a copy instead of a list value
    Console.WriteLine(listOfMutables[0].I);

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.但是当程序行为与预期不同时,有太多的极端情况,这可能导致难以产生和难以理解的微妙错误。

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. Fx,拥有一个数学值(例如整数、向量等)然后能够对其进行修改是没有意义的。 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.正如 Konrad 所提到的,更改日期也没有意义,因为该值表示该唯一的时间点,而不是具有任何状态或上下文相关性的时间对象的实例。

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.可以肯定的是,它更多地是关于您尝试用值类型捕获的概念,而不是实际细节。

If you have ever programmed in a language like C/C++, structs are fine to use as mutable.如果您曾经用 C/C++ 之类的语言进行过编程,那么结构体可以用作可变的。 Just pass them with ref, around and there is nothing that can go wrong.只需将它们与 ref, around 一起传递,就不会出错。 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).我发现的唯一问题是 C# 编译器的限制,并且在某些情况下,我无法强制愚蠢的事情使用对结构的引用,而不是复制(就像当结构是 C# 类的一部分时)。

So, mutable structs are not evil, C# has made them evil.因此,可变结构并不邪恶,C#使它们变得邪恶。 I use mutable structs in C++ all the time and they are very convenient and intuitive.我一直在 C++ 中使用可变结构,它们非常方便和直观。 In contrast, C# has made me to completely abandon structs as members of classes because of the way they handle objects.相比之下,C# 使我完全放弃结构作为类的成员,因为它们处理对象的方式。 Their convenience has cost us ours.他们的便利让我们付出了代价。

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 .如果您坚持结构的用途(在 C#、Visual Basic 6、Pascal/Delphi、C++ 结构类型(或类)中,当它们不用作指针时),您会发现结构只不过是一个复合变量. 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.我知道这会让很多习惯于 OOP 的人感到困惑,但是如果使用得当,这不足以说明这些东西本质上是邪恶的。 Some structures are inmutable as they intend (this is the case of Python's namedtuple ), but it is another paradigm to consider.有些结构是inmutable因为他们打算(这是Python的情况下namedtuple ),但它是另一种模式的考虑。

Yes: structs involve a lot of memory, but it will not be precisely more memory by doing:是的:结构涉及大量内存,但通过执行以下操作不会获得更多内存:

point.x = point.x + 1

compared to:相比:

point = Point(point.x + 1, point.y)

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.在 POO 中,对象的主要属性是其identity ,大多数情况下不超过其内存地址。 Struct stands for data structure (not a proper object, and so they don't have identity anyhow), and data can be modified. Struct 代表数据结构(不是一个合适的对象,所以它们无论如何都没有身份),并且数据可以被修改。 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).在其他语言中, record (而不是struct ,就像 Pascal 的情况一样)是这个词并且具有相同的目的:只是一个数据记录变量,旨在从文件中读取、修改和转储到文件中(这是主要的使用,并且在许多语言中,您甚至可以在记录中定义数据对齐,而正确调用对象的情况不一定如此)。

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. Python 有这个库是因为它是面向对象的,不支持结构体,所以它必须以另一种方式实现它,这有点难看。 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).尝试使用 Pascal 或 C 等语言读取具有适当结构的位图标头。这很容易(如果结构正确构建和对齐;在 Pascal 中,您不会使用基于记录的访问,而是使用函数来读取任意二进制数据)。 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).至于今天,我们已经习惯了 JSON 和 XML,所以我们忘记了二进制文件的使用(作为副作用,结构的使用)。 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.如果你用锤子来思考,你会想把螺丝当钉子,发现螺丝更难插在墙上,是螺丝的错,是坏的。

Imagine you have an array of 1,000,000 structs.假设您有一个包含 1,000,000 个结构的数组。 Each struct representing an equity with stuff like bid_price, offer_price (perhaps decimals) and so on, this is created by C#/VB.每个结构都代表一个股权,其中包含bid_price、offer_price(可能是小数)等,这是由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).想象一下,C#/VB 代码正在侦听价格变化的市场馈送,该代码可能必须访问数组的某些元素(无论哪种安全),然后修改某些价格字段。

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.他们需要这样做,因为以这些速率复制一些 120 字节的结构是疯狂的,尤其是当更新实际上可能只影响一两个字节时。

Hugo雨果

When something can be mutated, it gains a sense of identity.当某些东西可以变异时,它就会获得一种认同感。

struct Person {
    public string name; // mutable
    public Point position = new Point(0, 0); // mutable

    public Person(string name, Point position) { ... }
}

Person eric = new Person("Eric Lippert", new Point(4, 2));

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 .因为Person是可变的,所以考虑改变 Eric 的位置比克隆 Eric、移动克隆和破坏原始. Both operations would succeed in changing the contents of eric.position , but one is more intuitive than the other.这两种操作都可以成功更改eric.position的内容,但一种比另一种更直观。 Likewise, it's more intuitive to pass Eric around (as a reference) for methods to modify him.同样,将 Eric 传递给(作为参考)用于修改他的方法更直观。 Giving a method a clone of Eric is almost always going to be surprising.给一个方法一个 Eric 的克隆几乎总是会令人惊讶。 Anyone wanting to mutate Person must remember to ask for a reference to Person or they'll be doing the wrong thing.任何想要改变Person必须记住要求对Person的引用,否则他们会做错事。

If you make the type immutable, the problem goes away;如果您使类型不可变,问题就会消失; if I can't modify eric , it makes no difference to me whether I receive eric or a clone of eric .如果我不能修改eric ,这都没有区别,以我是否收到eric或克隆eric More generally, a type is safe to pass by value if all of its observable state is held in members that are either:更一般地说,如果一个类型的所有可观察状态都保存在以下任一成员中,则该类型可以安全地按值传递:

  • immutable不可变的
  • reference types引用类型
  • safe to pass by value按值传递是安全的

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.一个不可变的Person的直观性取决于你想要做什么。 If Person just represents a set of data about a person, there's nothing unintuitive about it;如果Person只是代表一个Person组数据,那么它没有什么不直观的; Person variables truly represent abstract values , not objects. Person变量真正代表抽象,而不是对象。 (In that case, it'd probably be more appropriate to rename it to PersonData .) If Person 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. (在这种情况下,将其重命名为PersonData可能更合适。)如果Person实际上是在模拟一个人本身,那么即使您已经避免了认为自己的陷阱,不断创建和移动克隆的想法也是愚蠢的。重新修改原文。 In that case it'd probably be more natural to simply make Person a reference type (that is, a class.)在这种情况下,简单地使Person成为引用类型(即类)可能会更自然。

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.诚然,正如函数式编程告诉我们的那样,让一切都不可变是有好处的(没有人可以秘密地持有对eric的引用并改变他),但是由于这在 OOP 中不是惯用的,因此对于其他使用它的人来说仍然是不直观的你的代码。

There are several issues with Mr. Eric Lippert's example. Eric Lippert 先生的例子有几个问题。 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.看这个例子,我认为这是一个糟糕的编程习惯的结果,而不是结构或类的真正问题。

  1. 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.你真的不需要两个结构来表达同样的事情。

  2. 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.正确的实现如下。

struct Mutable {
public int x;
}

class Test {
    private Mutable m = new Mutable();
    public int mutate()
    { 
        m.x = m.x + 1;
        return m.x;
    }
  }
  static void Main(string[] args) {
        Test t = new Test();
        System.Console.WriteLine(t.mutate());
        System.Console.WriteLine(t.mutate());
        System.Console.WriteLine(t.mutate());
    }

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:更改的结果 voila 的行为符合预期:

1 2 3 Press any key to continue . 1 2 3 按任意键继续。 . . . .

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 eg keys in a hash map.它与结构无关(也与 C# 无关),但在 Java 中,当可变对象是哈希映射中的键时,您可能会遇到可变对象的问题。 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. The Art of the Interpreter详细介绍了这些权衡,并给出了一些示例。

Personally when I look at code the following looks pretty clunky to me:就我个人而言,当我查看代码时,以下内容对我来说非常笨拙:

data.value.set ( data.value.get () + 1 ) ; data.value.set(data.value.get()+1);

rather than simply而不是简单地

data.value++ ;数据值++; or data.value = data.value + 1 ;或 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?但是,当您拥有公共 set 和 get 函数时,它们所做的只是将值设置为传入的内容,与简单地传递公共数据结构相比,这有何改进?

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.对我来说,这会阻止有效使用用于组织公共变量的结构,如果我想要访问控制,我会使用一个类。

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).使用 Eric 示例,也许您想创建该 Eric 的第二个实例,但要进行调整,因为这是您测试的性质(即复制,然后修改)。 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.如果我们只是在测试脚本的其余部分使用 Eric2,那么 Eric 的第一个实例会发生什么并不重要,除非您打算使用他作为测试比较。

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.这对于测试或修改浅定义特定对象(结构点)的遗留代码非常有用,但是通过具有不可变结构,这可以防止它的使用烦人。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM