使用LINQ Select创建新对象时,foreach修改属性的变更为何在循环外丢失?
这不是Bug,是LINQ Select的设计特性!
这绝对不是Bug,而是你对LINQ里Select()方法的作用理解有点偏差,咱们一步步说清楚:
核心原因:Select生成的是全新对象实例
当你用Select(x => new 你的类 { ... })的时候,每一次枚举这个Enumerable,都会生成一批全新的对象——这些对象和原集合里的元素完全是独立的实例,没有任何引用关系。你在foreach循环里修改的是这些临时生成的新对象的属性,循环结束后如果没有其他地方引用它们,这些对象会被GC回收,自然看起来像是“变更丢失”了;而原集合里的元素从头到尾根本没被碰过,当然也不会有变化。
对比两种场景,你就懂了
场景1:遍历原集合修改属性(变更会保留)
如果你遍历的是原集合(比如List<T>),且元素是引用类型,foreach拿到的是原对象的引用,修改属性自然会同步到原集合:
public class User { public string Name { get; set; } } var userList = new List<User> { new User { Name = "Alice" } }; foreach (var user in userList) { user.Name = "Bob"; } // 这里userList[0].Name已经变成"Bob"了,变更保留
场景2:用Select生成新对象后修改(变更“丢失”)
而如果是用Select()新建对象,情况完全不同:
var userList = new List<User> { new User { Name = "Alice" } }; // Select里生成了全新的User对象 var query = userList.Select(x => new User { Name = x.Name }); foreach (var user in query) { user.Name = "Bob"; } // 原userList[0].Name还是"Alice",因为你改的是Select生成的临时对象 // 甚至你再次遍历query,会发现又拿到了Name为"Alice"的新对象——因为每次枚举query都会重新执行Select逻辑生成新实例
怎么解决?
如果你希望修改后的结果能保留,有几种选择:
- 直接遍历原集合进行修改,跳过
Select()生成新对象的步骤; - 如果必须用
Select(),不要在里面新建对象,而是返回原集合元素的引用(比如Select(x => x),但这其实没必要,直接遍历原集合就行); - 把
Select()的结果转换成一个持久化的集合(比如ToList()),修改这个新集合里的对象——但要注意,这时候修改的是新集合里的实例,和原集合无关。
内容的提问来源于stack exchange,提问作者Robert Corell
相关产品推荐
相关产品推荐

