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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/441309/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 03:29:56  来源:igfitidea点击:

Why are mutable structs “evil”?

c#structimmutabilitymutable

提问by Dirk Vollmar

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# 中可变性和结构的实际问题是什么?

采纳答案by trampster

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)

如果你想改变它,你必须有意识地通过使用修改后的数据创建结构的新实例来进行。(不是副本)

回答by Marc Gravell

Where to start ;-p

从哪里开始;-p

Eric Lippert's blogis 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 tendto have multiple properties; yet if you have a struct with two ints, a string, a DateTimeand a bool, 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).

不太重要的是,存在尺寸问题;可变对象往往具有多个属性;但是,如果您有一个包含两个ints、a string、aDateTime和 a 的结构bool,您会很快消耗大量内存。对于一个类,多个调用者可以共享对同一个实例的引用(引用很小)。

回答by Bombe

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.

它与结构无关(也与 C# 无关),但在 Java 中,当可变对象是哈希映射中的键时,您可能会遇到可变对象的问题。如果在将它们添加到地图后更改它们并更改其哈希码,则可能会发生邪恶的事情。

回答by Konrad Rudolph

I wouldn't say evilbut 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 thinksthat it mightbe 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.

另外,我声称几乎从来不需要可变性,程序员只是认为可能在未来。例如,更改日期根本没有意义。相反,根据旧日期创建一个新日期。这是一个廉价的操作,所以性能不是一个考虑因素。

回答by Andru Luvisi

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 Interpretergoes into these trade offs in some detail, and gives some examples.

The Art of the Interpreter详细介绍了这些权衡,并给出了一些示例。

回答by Morten Christiansen

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.

值类型基本上代表不可变的概念。Fx,拥有一个数学值(例如整数、向量等)然后能够对其进行修改是没有意义的。这就像重新定义一个值的含义。与其更改值类型,不如分配另一个唯一值更有意义。考虑通过比较其属性的所有值来比较值类型的事实。关键是,如果属性相同,那么它就是该值的相同通用表示。

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.

希望这对你有意义。可以肯定的是,它更多地是关于您尝试用值类型捕获的概念,而不是实际细节。

回答by Hugo

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.

假设您有一个包含 1,000,000 个结构的数组。每个结构都代表一个股权,其中包含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

雨果

回答by Mike

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.

对我来说,这会阻止有效使用用于组织公共变量的结构,如果我想要访问控制,我会使用一个类。

回答by Sergey Teplyakov

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 Mutablestruct and we're calling the IncrementImethod 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?

假设我们有一个Mutable结构体数组,并且我们正在IncrementI为该数组的第一个元素调用该方法。您期望此调用有什么行为?它应该改变数组的值还是只改变一个副本?

    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.

因此,只要您和团队的其他成员清楚地了解您在做什么,可变结构就不是邪恶的。但是当程序行为与预期不同时,有太多的极端情况,这可能导致难以产生和难以理解的微妙错误。

回答by supercat

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.

改变“this”的结构方法(与属性设置器不同)有点邪恶,只是因为.net没有提供将它们与没有的方法区分开来的方法。不改变“this”的结构方法即使在只读结构上也应该是可调用的,而无需任何防御性复制。改变“this”的方法根本不应该在只读结构上调用。由于 .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?
  2. If no outside references to 'foo' exist before the method is called, could an outside reference exist after?
  1. 假设该方法不使用任何“不安全”代码,它是否会修改 foo?
  2. 如果在调用该方法之前不存在对 'foo' 的外部引用,那么在调用方法之后是否会存在外部引用?

Answers:

答案:

Question 1:
Method1(): no (clear intent)
Method2(): yes (clear intent)
Method3(): yes (uncertain intent)
Question 2:
Method1(): no
Method2(): no (unless unsafe)
Method3(): yes

问题 1::
Method1()(意图明确)
Method2():是(意图明确)
Method3():是(意图不确定)
问题 2
Method1()::否
Method2():否(除非不安全)
Method3():是

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.

Method1 不能修改 foo,并且永远不会得到引用。Method2 获得对 foo 的短期引用,它可以使用它以任何顺序修改 foo 的字段任意次数,直到它返回,但它不能保留该引用。在 Method2 返回之前,除非它使用不安全的代码,否则任何和所有可能由其 'foo' 引用制成的副本都将消失。与 Method2 不同的是,Method3 获得了一个对 foo 的可混杂共享的引用,并且不知道它可以用它做什么。它可能根本不会改变 foo,它可能会改变 foo 然后返回,或者它可能会将 foo 的引用提供给另一个线程,该线程可能会在未来某个任意时间以某种任意方式改变它。

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.

结构数组提供了美妙的语义。给定 Rectangle 类型的 RectArray[500],很明显如何例如将元素 123 复制到元素 456,然后在一段时间后将元素 123 的宽度设置为 555,而不会干扰元素 456。“RectArray[432] = RectArray[321 ]; ...; RectArray[123].Width = 555;". 知道 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. 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.

现在假设 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] ]); RectClassArray[321].X = 555;" 要知道应该做什么,必须对 RectClass 了解更多(例如,它是否支持复制构造函数、复制自方法等)?) 和数组的预期用途。远不如使用结构那么干净。

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).

可以肯定的是,不幸的是,除了数组之外的任何容器类都没有很好的方法来提供结构数组的清晰语义。最好的办法是,如果想要使用例如字符串对集合进行索引,则可能是提供一个通用的“ActOnItem”方法,该方法将接受一个字符串作为索引、一个通用参数和一个将被传递的委托通过引用泛型参数和集合项。这将允许与 struct 数组几乎相同的语义,但除非 vb.net 和 C# 人员可以提供良好的语法,否则即使代码具有合理的性能(传递泛型参数会允许使用静态委托,并且无需创建任何临时类实例)。

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.

就个人而言,我对 Eric Lippert 等人的仇恨感到恼火。关于可变值类型的喷涌。与到处使用的混杂引用类型相比,它们提供了更清晰的语义。尽管 .net 对值类型的支持存在一些限制,但在许多情况下,可变值类型比任何其他类型的实体都更适合。