打印以下代码(调用MyMethod时):
0
0
0
1
我希望它能打印出来:
0
0
1
1
为什么会这样?
代码:
private struct MyStruct
{
public MyInnerStruct innerStruct;
}
private struct MyInnerStruct
{
public int counter;
public void AddOne()
{
++counter;
}
}
public static void MyMethod()
{
MyStruct[] myStructs = new MyStruct[] { new MyStruct() };
foreach (var myStruct in myStructs)
{
MyStruct myStructCopy = myStruct;
Console.WriteLine(myStruct.innerStruct.counter);
Console.WriteLine(myStructCopy.innerStruct.counter);
myStruct.innerStruct.AddOne();
myStructCopy.innerStruct.AddOne();
Console.WriteLine(myStruct.innerStruct.counter);
Console.WriteLine(myStructCopy.innerStruct.counter);
}
}
答案 0 :(得分:4)
您看到此行为的原因必须是使用迭代变量。迭代变量是只读的,因为在C#中你无法修改它们(C#lang spec section 8.8.4详细说明了这个
迭代变量对应于只读局部变量,其范围扩展到嵌入语句
使用只读可变结构是一种意外行为的途径。您不是直接使用变量,而是实际使用变量的副本。因此,在myStruct
的情况下,副本会增加,而不是实际值。这就是原始值保持不变的原因。
Eric在这个主题上做了一篇相当深入的文章,你可以在这里访问
另一个原因是你总是具有不可变的结构。
答案 1 :(得分:1)
迭代变量是只读的,通常禁止直接修改只读结构,或者通过ref
将这样的结构或其任何部分传递给任何修改它的代码。不幸的是,有一个皱纹:尽管许多结构方法和属性不会改变底层结构,但是在结构上调用方法或属性要求它由ref
传递,无论方法或属性是否会改变struct实例。这给C#的设计者留下了五个选择:
选择#1会有点烦人,特别是关于通过属性暴露其状态的结构。从性能角度来看,选择#2或#4会很好,但是意外编写this
的结构例程可能会导致意外行为。微软选择了#3(除了添加可选属性外,选择#5将是相同的)。在许多方面,这是最糟糕的选择(不写this
的方法比没有复制操作的方法运行得慢,写this
的方法无论是否有效都无法正常工作,当一个struct写一个只读变量时,唯一被损坏的东西就是一个代码已被破坏的结构的实例。尽管如此,这种选择现在已经很成熟,而且它就是这样。
顺便提一下,即使是嵌入在结构类型的只读结构或属性中的读取操作结构也可能比使用非只读字段要昂贵得多,而只是避免编写它们。调用myStruct.innerStruct.AddOne()
时,编译器首先复制MyStruct
,然后复制myStruct.InnerStruct
,然后调用AddOne()
方法。相比之下,在调用myStructCopy.innerStruct.AddOne()
时,它可以通过innerStruct
方法引用AddOne()
而不复制任何内容。
答案 2 :(得分:0)
我认为你会发现MyStruct.innerStruct实际上返回了struct的副本,而不是struct本身。因此,您正在增加副本的价值......
当然,我最近在某个地方读过一篇关于此事的博客。
如果您更改以下内容
myStruct.innerStruct.AddOne();
Console.WriteLine(myStruct.innerStruct.counter);
到
MyInnerStruct inner = myStruct.innerStruct;
inner.AddOne();
Console.WriteLine(inner.counter);
然后你应该看到它开始工作了。