【问题标题】:Why can't we change the iteration variable in a foreach loop为什么我们不能在 foreach 循环中更改迭代变量
【发布时间】:2012-03-30 20:16:15
【问题描述】:
                var names = new[] { 
                            new { Name = "John", Age = 44 },
                            new { Name = "Diana", Age = 45 },
                            new { Name = "James", Age = 17 },
                            new { Name = "Francesca", Age = 15} 
                            };

            for (int i = 0; i < names.Length; i++)
            {
                names[i].Age = 23; //-------->Error
                names[i] = new { Name = "XYX", Age = 26 }; //----->Works fine
            }

            foreach(var name in names)
            {
                name.Age = 1;  //-------->Error
                name = new { Name = "ABC", Age = 25 };  //-------->Error
            }

我有两个问题。 1. 为什么我无法更改迭代变量的 any 属性。
2. 我只能将一个新对象分配给 for 循环中的迭代变量。不在 foreach 循环中。为什么?

【问题讨论】:

    标签: c# .net foreach


    【解决方案1】:

    问题 1:为什么我无法更改迭代变量的 any 属性?

    来自Anonymous Types上的文档:

    匿名类型提供了一种方便的方式来封装一组只读属性

    您无法更改匿名类型中的属性值,因此

    name.Age = 1;
    // and
    names[i].Age = 1; 
    

    同样无效。


    问题 2。我只能将一个新对象分配给 for 循环中的迭代变量。不在 foreach 循环中。为什么?

    来自IEnumerable上的文档:

    只要集合保持不变,枚举数就保持有效。

    如果以任何方式更改后备列表,都会使迭代器无效。例如,考虑一下如果迭代器根据 Age 字段以特定顺序返回项目会发生什么。

    【讨论】:

    • 注意:这是对问题 1 的回答。
    • 我一直不同意你的观点,直到你提到排序问题。现在我明白了。非常感谢!
    【解决方案2】:

    为什么我无法更改迭代变量的 any 属性。

    您正在使用匿名类型,这些类型在 C# 中始终具有只读属性。 (在 VB 中,默认情况下它们是读/写的,但可以使用 Key 修饰符将其设为只读。)

    来自 C# 4 规范,第 7.6.10.6 节:

    匿名对象初始化器声明一个匿名类型并返回该类型的实例。匿名类型是直接从object 继承的无名类类型。匿名类型的成员是从用于创建该类型实例的匿名对象初始化程序中推断出来的一系列只读属性。

    关于你的第二个问题...

    我只能将一个新对象分配给 for 循环中的迭代变量。不在 foreach 循环中。为什么?

    语言规范是这样定义的。特别是,即使您可以更改变量,也不会更改数组,除非语言规范使其仅适用于数组。通常,foreach 使用 IEnumerable/IEnumerator(或类似的成员),它只提供序列的“阅读”视图。

    来自 C# 4 规范的第 8.8.4 节:

    迭代变量对应一个只读局部变量,其作用域延伸到嵌入语句。

    (重要的是,虽然它是一个只读变量,但它的值在迭代之间会发生变化。在 C# 5 中,这将被更改,以便在每次迭代时它实际上是一个“新”变量。差异仅在变量为被诸如 lambda 表达式之类的东西捕获。)

    【讨论】:

    • 谢谢,特别是,即使您可以更改变量,也无法更改数组。这个变量是否包含原始对象的副本。
    • @vaibhav:不,它保存了对原始对象的reference 的副本。该数组也仅包含引用。如果要更改数组中的值,则需要更改数组内容。仅仅改变一个变量,它的值是数组中引用的 copy 不会做任何事情。
    • names[i] = new object,在这个语句中,我改变的是原始对象,而不是它的副本。
    • @vaibhav:不,您正在更改 array 的内容。数组元素现在引用不同的对象。您没有对数组元素先前引用的对象进行任何更改。
    【解决方案3】:

    至于问题 2):在“工作正常”的情况下,您不会更改迭代变量。在这种情况下,迭代变量是i。您所做的是用新元素替换数组的一个元素。这总是有效的。

    【讨论】:

      【解决方案4】:

      因为 foreach 使用枚举器,并且枚举器不能更改底层集合,但是可以更改集合中对象引用的任何对象。这就是值和引用类型语义发挥作用的地方。

      在一个引用类型,即一个类上,所有的集合存储的是一个对象的引用。因此,它从未真正接触过该对象的任何成员,也不会不在乎它们。对对象的更改不会触及集合。

      另一方面,值类型将其整个结构存储在集合中。如果不更改集合并使枚举器无效,则无法触摸其成员。

      此外,枚举器返回集合中值的副本。在 ref 类型中,这没有任何意义。引用的副本将是相同的引用,并且您可以以任何您想要的方式更改引用的对象,而更改超出范围。另一方面,值类型意味着您得到的只是对象的副本,因此对所述副本的任何更改都不会传播。

      【讨论】:

        猜你喜欢
        • 2012-02-25
        • 1970-01-01
        • 2012-06-29
        • 2012-08-02
        • 2019-04-12
        • 2023-03-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多