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

Unity中Linq Select为何会使新对象引用原构造参数对象?

Unity协程中Linq与List存储轮廓颜色的差异解析

问题描述

在Unity+C#开发游戏时,我在敌人的初始化协程里需要完成三个步骤:

  1. 将敌人轮廓设为透明
  2. 等待加载动画完成
  3. 将轮廓恢复为原始颜色

首次尝试用Linq的Select生成IEnumerable<(Outline, Color)>集合,第一次遍历修改轮廓为透明后,等待完成再第二次遍历,发现元组里的Color变成了透明值;但改用List<(Outline, Color)>提前存储的方式却能正常恢复原始颜色。想知道这两种实现的差异,以及匿名函数创建对象和for循环创建的本质区别。

首次尝试(存在问题的代码)

IEnumerable<(Outline, Color)> outlines = GetComponentsInChildren<Outline>()
    .Select(delegate (Outline outline) 
    {
        float r = outline.effectColor.r;
        float g = outline.effectColor.g;
        float b = outline.effectColor.b;
        float a = outline.effectColor.a;
        return (outline, new Color(r, g, b, a));
    });
foreach ((Outline outline, Color _) in outlines) {
    Color transparent = Vector4.zero;
    outline.effectColor = transparent;
}
yield return new WaitForSeconds(LOAD_TIME);
// ...
foreach ((Outline outline, Color effectColor) in outlines)
{
    // 奇怪的是,这里的effectColor变成了Vector4.zero
    // 如果上面把effectColor设为Color.cyan,这里也会变成cyan
    outline.effectColor = effectColor;
}

可行代码

// ...
List<(Outline, Color)> outlines = new List<(Outline, Color)>();
foreach (Outline outline in GetComponentsInChildren<Outline>())
{
    float r = outline.effectColor.r;
    float g = outline.effectColor.g;
    float b = outline.effectColor.b;
    float a = outline.effectColor.a;                
    outlines.Add((outline, new Color(r, g, b, a)));
}
foreach ((Outline outline, Color _) in outlines) {
    outline.effectColor = Vector4.zero;
}
yield return new WaitForSeconds(LOAD_TIME);
// ...
foreach ((Outline outline, Color effectColor) in outlines)
{
    // 这里的effectColor是正确的原始颜色
    outline.effectColor = effectColor;
}

核心原因:LINQ的延迟执行特性

这两种实现的本质差异在于**IEnumerable<T>的延迟执行和List<T>的立即执行**:

  • Linq Select生成的IEnumerable是延迟执行的:
    你第一次遍历outlines时,才会真正触发Select里的匿名函数执行,此时读取outline.effectColor生成元组,紧接着修改轮廓为透明。而第二次遍历outlines时,Select的逻辑会重新跑一遍——这时候outline.effectColor已经是透明值了,生成的元组自然也带着透明Color,导致恢复失败。
  • List<T>是立即执行的:
    在初始化List的foreach循环里,会一次性读取所有outline的原始颜色,生成元组并存储到List中。后续两次遍历的都是List里提前保存好的元组,这些Color值不会受之后修改outline.effectColor的影响,所以第二次遍历能正确恢复原始颜色。

关于匿名函数和for循环创建对象的区别

这里的核心不是匿名函数和for循环的差异,而是执行时机:
Linq的匿名函数是在每次遍历IEnumerable时才执行,而for循环里的对象创建是在初始化List时就完成了,一次性把所有原始颜色固化存储下来,后续不会再重新读取outline.effectColor。哪怕把Linq的匿名函数换成lambda表达式,结果还是一样的——问题根源是IEnumerable的延迟执行,和匿名函数本身无关。

内容的提问来源于stack exchange,提问作者Adam

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 02:12:37