在这里的SO讨论之后,我已经多次看到这样的评论,即可变结构体是“邪恶的”(就像在这个问题的答案中)。

c#中的可变性和结构的实际问题是什么?


当前回答

结构是值类型,这意味着它们在传递时被复制。

所以如果你改变了一份副本,你只是改变了那份副本,而不是原件,也不是周围可能存在的其他副本。

如果你的struct是不可变的,那么所有通过值传递的自动副本都是相同的。

如果你想要改变它,你必须有意识地用修改过的数据创建一个结构的新实例。(非副本)

其他回答

我不会说可变性是邪恶的,但可变性通常是程序员急于提供最大功能的标志。在现实中,这通常是不需要的,反过来,使界面更小,更容易使用,更难使用错误(=更健壮)。

其中一个例子就是竞态条件中的读/写和写/写冲突。这些在不可变结构中根本不可能发生,因为写操作不是有效的操作。

另外,我认为可变性几乎从来都不是真正需要的,程序员只是认为它在未来可能会出现。例如,改变日期是没有意义的。相反,在旧日期的基础上创建一个新的日期。这是一个廉价的操作,所以性能不是一个考虑因素。

假设您有一个包含1,000,000个结构体的数组。每个结构体都用bid_price、offer_price(可能是小数)等表示权益,这是由c# /VB创建的。

假设数组是在非托管堆中分配的内存块中创建的,以便其他一些本地代码线程能够并发地访问该数组(可能是一些高性能代码进行数学运算)。

想象一下c# /VB代码正在监听价格变化的市场反馈,该代码可能必须访问数组的某些元素(用于任何安全性),然后修改一些价格字段。

想象一下,这个过程以每秒数万次甚至数十万次的速度进行。

让我们面对现实吧,在这种情况下,我们确实希望这些结构体是可变的,它们必须是可变的,因为它们被其他本地代码共享所以创建副本是没有用的;他们需要这样做,因为以这样的速率复制大约120字节的结构是疯狂的,特别是当一个更新实际上可能只影响一两个字节时。

Hugo

从哪里开始;-p

埃里克·利珀特的博客总是很适合引用:

这是可变的另一个原因 值类型是邪恶的。试着总是 使值类型不可变。

首先,您很容易丢失更改……例如,从列表中获取内容:

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

这改变了什么?没有什么有用的…

属性也是一样:

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

强迫你做:

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

不那么关键的是规模问题;可变对象往往有多个属性;然而,如果你有一个包含两个int型,一个string型,一个DateTime型和一个bool型的结构体,你会很快消耗大量内存。使用类,多个调用方可以共享对同一个实例的引用(引用很小)。

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.

正如Konrad所提到的,更改日期也没有意义,因为值代表的是唯一的时间点,而不是具有任何状态或上下文依赖关系的时间对象的实例。

希望这能让你明白。可以肯定的是,它更多的是关于您试图用值类型捕获的概念,而不是实际的细节。

从程序员的角度来看,还有一些其他的极端情况可能导致不可预测的行为。

不可变值类型和只读字段

    // 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);
        }
    }

可变值类型和数组

假设我们有一个可变结构的数组,我们为该数组的第一个元素调用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);

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