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

C# 为什么可变结构是“;邪恶”;?,c#,struct,immutability,mutable,C#,Struct,Immutability,Mutable,在这里的讨论之后,我已经读了好几遍关于可变结构是“邪恶”的评论(就像在这个问题的答案中) C#中的易变性和结构的实际问题是什么 从哪里开始-p 对于报价来说总是很好的: 这是可变的另一个原因 值类型是邪恶的。试着总是 使值类型不可变 首先,您很容易丢失更改。。。例如,从列表中获取内容: Foo foo = list[0]; foo.Name = "abc"; 这有什么变化?没有什么有用的 与属性相同: myObj.SomeProperty.Size = 22; // the compiler

在这里的讨论之后,我已经读了好几遍关于可变结构是“邪恶”的评论(就像在这个问题的答案中)

C#中的易变性和结构的实际问题是什么

从哪里开始-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
的结构,一个
字符串
,一个
日期时间
和一个
布尔
,你可以很快地消耗大量内存。对于一个类,多个调用方可以共享对同一实例的引用(引用很小)。

这与结构无关(也与C#无关),但在Java中,当可变对象是哈希映射中的键时,可能会遇到问题。如果您在将它们添加到地图后更改它们,并且它更改了它的属性,那么可能会发生不好的事情。

我不会说不好,但对于程序员来说,为了提供最大的功能,可变性往往是一种过度热情的表现。实际上,这通常是不需要的,这反过来又使接口更小、更容易使用、更难使用(=更健壮)

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


,程序员只是认为它可能在未来。例如,更改日期根本没有意义。相反,根据旧日期创建新日期。这是一个廉价的操作,因此性能不是一个考虑因素。

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

因此,如果你改变了一份副本,你只改变了那份副本,而不是原件,也不是可能存在的任何其他副本


如果您的结构是不可变的,那么通过值传递产生的所有自动副本都是相同的


如果您想更改它,您必须有意识地使用修改后的数据创建一个新的结构实例。(不是副本)

可变数据有许多优点和缺点。百万美元的缺点是别名。如果相同的值在多个位置使用,并且其中一个位置更改了该值,则该值似乎已神奇地更改为其他使用该值的位置。这与比赛条件有关,但不完全相同

百万美元的优势有时是模块化。可变状态允许您对不需要了解的代码隐藏更改信息


详细讨论了这些权衡,并给出了一些示例。

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

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


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

假设您有一个1000000个结构的数组。每个结构都代表一个权益,包含出价、出价(可能是小数)等内容,这是由C#/VB创建的

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

假设C#/VB代码正在侦听价格变化的市场反馈,该代码可能必须访问数组的某些元素(对于任何一种证券),然后修改某些价格字段

想象一下,这是每秒数十次甚至数十万次的

让我们面对事实,在这种情况下,我们确实希望这些结构是可变的,它们必须是可变的,因为它们被其他一些本机代码共享,所以创建副本是没有帮助的;它们必须是这样的,因为以这样的速率复制一个120字节的结构是愚蠢的,特别是当一个更新实际上可能只影响一两个字节时


Hugo

就我个人而言,当我看代码时,以下内容看起来相当笨拙:

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

而不仅仅是

data.value++;或data.value=data.value+1

数据封装在传递类时非常有用,并且您希望确保以受控方式修改值。然而,当您拥有公共set和get函数时,这些函数所做的仅仅是将值设置为传入的值,这与简单地传递公共数据结构相比有何改进

当我在类中创建私有结构时,我创建该结构是为了将一组变量组织到一个组中。我希望能够在类范围内修改该结构,而不是获取该结构的副本并创建新实例


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

还有一些其他情况可能会导致程序出现不可预测的行为
    // 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[] 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);
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);
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));
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());
    }
point.x = point.x + 1
point = Point(point.x + 1, point.y)