C# foreach 是否从 IEnumerable 创建元素的副本?

Wei*_*Liu 2 c# linq foreach ref

在什么情况下 foreach 使用引用,在什么情况下 foreach 使用副本?

using System;
using System.Linq;

class A {
    public int v;
}

class Program
{
    static void Main() {
        var ints = new int[] { 0, 1, 2 };
        
        var array = ints.Select(i=>new A {v = i}).ToArray();
        foreach(var a in array) {
            a.v = 999;
        }

        var enumerable = ints.Select(i=>new A {v = i});
        foreach(var a in enumerable) {
            a.v = 999;
        }

        Console.WriteLine($"array.First = {array.First().v}");
        Console.WriteLine($"enumerable.First = {enumerable.First().v}");
    }
}
Run Code Online (Sandbox Code Playgroud)

jdoodle.com/ia/Jce

输出:

array.First = 999
enumerable.First = 0
Run Code Online (Sandbox Code Playgroud)

似乎在foreach(var a in enumerable) {a 中是复制而不是引用,而在foreach(var a in array) {a 中是引用。

有人可以解释一下吗?

Swe*_*per 5

这并不是真的foreach。它更多地与你如何构造array和有关enumerableforeach在这两种情况下都执行相同的操作(获取枚举器并调用MoveNextandCurrent迭代可枚举值)。正是enumerable和之间的差异array导致了输出的差异。

array是一个A[],所以如果你改变v它的元素的 s ,然后获取这些元素的第一个,你会明显看到变化。A是引用类型,因此ainforeach是对数组中元素的引用。

enumerable然而,是由 产生的东西Select。如果您只是打电话,不会做任何实质性的事情Select。它只是创建一个IEnumerable<A>当枚举 时,会创建一堆A对象。这里重要的一点是,每当枚举Select时都会运行 lambda 。enumerable如果您不枚举它,则不会发生任何事情。这称为延迟执行

所以第二个foreach枚举enumerable,创建一堆A对象,然后更改v这些对象的 s A。这是重要的区别 -A与数组不同,对象不会存储在任何地方。A每次迭代后您都会“丢弃”该对象。

当您enumerable.First()最后调用时,您将enumerable再次开始枚举 - 这次只枚举一次,因为您只需要第一个元素。做什么呢enumerableA它通过运行 中的代码创建一个新对象Select