You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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延迟执行特性和数组的立即实例化的区别:

  1. 数组遍历的情况:
    ToArray()会立即执行Select逻辑,把所有A的实例创建出来并存储在数组中。foreach遍历数组时,循环变量a是指向数组中已存在的A实例的引用副本——这个副本指向的是数组里的同一个对象,所以修改a.v本质是修改数组中对应实例的属性值,后续调用array.First()拿到的就是被修改过的那个实例,自然输出999。

  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 01:17:29