在这里的SO讨论之后,我已经多次看到这样的评论,即可变结构体是“邪恶的”(就像在这个问题的答案中)。
c#中的可变性和结构的实际问题是什么?
在这里的SO讨论之后,我已经多次看到这样的评论,即可变结构体是“邪恶的”(就像在这个问题的答案中)。
c#中的可变性和结构的实际问题是什么?
当前回答
如果使用得当,我不相信它们是邪恶的。我不会把它放在我的生产代码中,但我会把它放在像结构化单元测试模拟这样的东西中,其中结构的生命周期相对较小。
使用Eric示例,也许您想要创建该Eric的第二个实例,但是要进行调整,因为这是您的测试的性质(即复制,然后修改)。如果我们只是在测试脚本的剩余部分中使用Eric2,那么Eric的第一个实例发生什么并不重要,除非您计划使用Eric2作为测试比较。
这对于测试或修改那些浅层定义特定对象(结构体的重点)的遗留代码非常有用,但是通过使用不可变的结构体,可以避免令人讨厌的使用。
其他回答
结构是值类型,这意味着它们在传递时被复制。
所以如果你改变了一份副本,你只是改变了那份副本,而不是原件,也不是周围可能存在的其他副本。
如果你的struct是不可变的,那么所有通过值传递的自动副本都是相同的。
如果你想要改变它,你必须有意识地用修改过的数据创建一个结构的新实例。(非副本)
它与结构无关(也与c#无关),但在Java中,当可变对象是哈希映射中的键时,你可能会遇到问题。如果你在将它们添加到映射后更改它们,它也更改了哈希代码,可能会发生糟糕的事情。
李柏特先生举的例子有几个问题。它是为了说明结构是复制的,以及如果你不小心的话,这可能是一个问题。看看这个例子,我认为这是一个坏的编程习惯的结果,而不是结构或类的问题。
结构应该只有公共成员,不需要任何封装。如果是这样,那么它真的应该是一个类型/类。你真的不需要两个构念来表达同一件事。 如果有一个类包含一个结构,则可以调用该类中的一个方法来更改成员结构。我认为这是一个良好的编程习惯。
正确的实现如下所示。
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());
}
看起来这是编程习惯的问题,而不是结构本身的问题。结构体应该是可变的,这是它的思想和意图。
更改的结果voila表现如预期:
1 2 3. 按任意键继续…
如果使用得当,我不相信它们是邪恶的。我不会把它放在我的生产代码中,但我会把它放在像结构化单元测试模拟这样的东西中,其中结构的生命周期相对较小。
使用Eric示例,也许您想要创建该Eric的第二个实例,但是要进行调整,因为这是您的测试的性质(即复制,然后修改)。如果我们只是在测试脚本的剩余部分中使用Eric2,那么Eric的第一个实例发生什么并不重要,除非您计划使用Eric2作为测试比较。
这对于测试或修改那些浅层定义特定对象(结构体的重点)的遗留代码非常有用,但是通过使用不可变的结构体,可以避免令人讨厌的使用。
从程序员的角度来看,还有一些其他的极端情况可能导致不可预测的行为。
不可变值类型和只读字段
// 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);
因此,只要您和团队的其他成员清楚地了解您在做什么,可变结构就不是邪恶的。但是有太多的极端情况,当程序行为与预期不同时,这可能会导致微妙的难以产生和难以理解的错误。