C#中foreach会复制IEnumerable元素吗?何时用引用/副本?
C#中foreach的元素复制与引用/副本场景解析
问题
- C#中的foreach语句会从IEnumerable中复制元素吗?
- 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}"); } }
输出结果
array.First = 999 enumerable.First = 0
现象解释
你看到的差异和foreach是否用引用/副本无关,核心是LINQ延迟执行特性和数组的立即实例化的区别:
数组遍历的情况:
ToArray()会立即执行Select逻辑,把所有A的实例创建出来并存储在数组中。foreach遍历数组时,循环变量a是指向数组中已存在的A实例的引用副本——这个副本指向的是数组里的同一个对象,所以修改a.v本质是修改数组中对应实例的属性值,后续调用array.First()拿到的就是被修改过的那个实例,自然输出999。IEnumerable遍历的情况:
LINQ的Select是延迟执行的,var enumerable = ints.Select(...)并没有创建任何A实例,只有当你开始枚举它(比如foreach循环、调用First())时,才会逐次执行lambda表达式创建新的A实例。
第一次foreach遍历enumerable时,每次迭代都生成一个新的A实例,修改a.v为999,但这些实例在遍历结束后就被丢弃了;当你调用enumerable.First()时,又会重新执行Select的lambda,创建一个全新的A实例(v=0),所以输出0。
foreach的引用/副本规则
foreach的行为只和元素的类型有关:
- 值类型(如int、自定义struct):foreach会复制元素的数值到循环变量中,修改循环变量不会影响原集合中的元素。
- 引用类型(如自定义class):foreach拿到的是对象引用的副本,但这个副本指向原集合中的同一个对象实例。因此修改对象的属性会影响原集合中的对象;但如果直接给循环变量赋值(比如
a = new A()),不会改变原集合中对应的引用。
内容的提问来源于stack exchange,提问作者Wei Liu
相关产品推荐
相关产品推荐

