在foreach循环中更改另一个结构内的结构

时间:2009-11-06 15:10:17

标签: c# .net struct foreach

打印以下代码(调用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);
    }
}

3 个答案:

答案 0 :(得分:4)

您看到此行为的原因必须是使用迭代变量。迭代变量是只读的,因为在C#中你无法修改它们(C#lang spec section 8.8.4详细说明了这个

  

迭代变量对应于只读局部变量,其范围扩展到嵌入语句

使用只读可变结构是一种意外行为的途径。您不是直接使用变量,而是实际使用变量的副本。因此,在myStruct的情况下,副本会增加,而不是实际值。这就是原始值保持不变的原因。

Eric在这个主题上做了一篇相当深入的文章,你可以在这里访问

另一个原因是你总是具有不可变的结构。

答案 1 :(得分:1)

迭代变量是只读的,通常禁止直接修改只读结构,或者通过ref将这样的结构或其任何部分传递给任何修改它的代码。不幸的是,有一个皱纹:尽管许多结构方法和属性不会改变底层结构,但是在结构上调用方法或属性要求它由ref传递,无论方法或属性是否会改变struct实例。这给C#的设计者留下了五个选择:

  1. 不允许在只读上下文中的结构上使用struct方法或属性,而不将结构显式复制到可变存储位置(很可能是局部变量)。
  2. 允许在只读上下文中的结构上使用struct方法或属性,并希望它们不会修改它们不应该的任何内容。
  3. 在只读结构上调用方法或属性时,以静默方式创建结构的副本,并将该副本提供给方法或属性。
  4. 提供一种方法,通过该方法,结构方法或属性可以声明性地指示它们是否会改变底层结构;只允许struct方法和属性修改它们自己的字段(如果它们被声明为这样做),并且只允许将只读结构传递给未声明为修改它们的方法。
  5. 提供一种方法,通过该方法,结构方法或属性可以声明性地指示它们是否会改变底层结构;禁止在只读上下文中对结构使用此类方法和属性,但在调用没有此类声明的方法时制作只读结构的防御副本,以防方法应该具有但不具备这些方法和属性。

选择#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);

然后你应该看到它开始工作了。