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

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


当前回答

当某种东西可以变异时,它就获得了一种认同感。

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. Both operations would succeed in changing the contents of eric.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 mutate Person must remember to ask for a reference to Person or they'll be doing the wrong thing.

如果你让类型是不可变的,这个问题就消失了;如果我不能修改eric,无论我收到eric还是eric的克隆对我来说都没有区别。更一般地说,如果类型的所有可观察状态都保存在以下成员中,则按值传递是安全的:

不可变的 引用类型 安全通过价值

如果满足这些条件,那么可变值类型的行为就像引用类型一样,因为浅拷贝仍然允许接收方修改原始数据。

The intuitiveness of an immutable Person depends on what you're trying to do though. If Person 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 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. In that case it'd probably be more natural to simply make Person a reference type (that is, a class.)

诚然,函数式编程已经告诉我们,使所有东西都不可变是有好处的(没有人可以秘密地保留对eric的引用并改变他),但由于这在OOP中不是惯用的,因此对于使用您的代码的其他人来说仍然是不直观的。

其他回答

从哪里开始;-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型的结构体,你会很快消耗大量内存。使用类,多个调用方可以共享对同一个实例的引用(引用很小)。

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

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

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

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

如果你曾经用C/ c++这样的语言编程,结构体可以作为可变的。只要把球传给裁判,没有什么会出错的。我发现的唯一问题是c#编译器的限制,在某些情况下,我无法强迫这个愚蠢的东西使用对结构的引用,而不是Copy(比如当结构是c#类的一部分时)。

所以,可变结构体不是邪恶的,是c#把它们变成了邪恶的。我一直在c++中使用可变结构体,它们非常方便和直观。相比之下,c#让我完全放弃了作为类成员的结构体,因为它们处理对象的方式。他们的便利让我们付出了代价。

它与结构无关(也与c#无关),但在Java中,当可变对象是哈希映射中的键时,你可能会遇到问题。如果你在将它们添加到映射后更改它们,它也更改了哈希代码,可能会发生糟糕的事情。

就我个人而言,当我看代码时,下面的代码看起来相当笨拙:

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

而不是简单地

数据.值++ ;或数据值 = 数据值 + 1 ;

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

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

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