Unity中Linq Select为何会使新对象引用原构造参数对象?
Unity协程中Linq与List存储轮廓颜色的差异解析
问题描述
在Unity+C#开发游戏时,我在敌人的初始化协程里需要完成三个步骤:
- 将敌人轮廓设为透明
- 等待加载动画完成
- 将轮廓恢复为原始颜色
首次尝试用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
相关产品推荐
相关产品推荐

