使用 Linq 后导致修改列表对象字段无效

最近使用 Linq 写了这样一段代码:

先用 Select 生成 IEnumerable<T>,然后通过 First()将对象从 IEnumerable<T> 中查找出来,接着修改该对象的字段值,最后将 IEnumerable<T> 转换成 List 返回。

我惊奇地发现,刚刚对第一个对象的修改,居然没有生效,这个 bug 让人促不及防。

伪代码复现

伪代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Person 类
internal class Person
{
public string Name { get; set; } = "defaultName";
public Person(string name)
{
Name = name;
Console.WriteLine("create a new person: ");
}

public void ShowSelf()
{
Console.WriteLine($"My name is {Name}");
}
}

使用代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 使用 Select 生成 IEnumerable<Person>
IEnumerable<Person> list = new List<string>(){
"ZhangSan","LiSi","WangWu"
}.Select(x => new Person(x));

var firstPerson = list.First();
Console.WriteLine();
Console.WriteLine($"I am the first person, my name was changed from {firstPerson.Name} to ZhangSanPro");
firstPerson.Name = "ZhangSanPro";
firstPerson.ShowSelf();

Console.WriteLine();
Console.WriteLine("persons in list:");
foreach (var person in list)
{
person.ShowSelf();
}
Console.ReadKey();

结果

1
2
3
4
5
6
7
8
9
10
11
12
create a new person:

I am the first person, my name was changed from ZhangSan to ZhangSanPro
My name is ZhangSanPro

persons in list:
create a new person:
create a new person:
create a new person:
My name is ZhangSan
My name is LiSi
My name is WangWu

从输出可以看到,对第一个对象的 Name 修改是生效,但是为什么后面转换成 List 后,它的值却没改变呢,按道理修改引用对象的字段,也会影响到 List 中的对象的。

原因分析

在上述代码中,Select 方法返回的是一个 IEnumerable<Person>,这是一个延迟执行的查询,意味着查询的结果(也就是 Person 对象的集合)直到第一次遍历它时才会被计算。因此,当修改 firstPersonName 属性后,再次遍历 list 时,Select 查询会再次执行,从而创建新的 Person 对象,这就是为什么看到的 Name 属性没有被修改。

因为了为保证修改的对象与遍历的对象是一致的,可以将其转换成 List,再执行后续操作

1
2
3
IEnumerable<Person> list = new List<string>(){
"ZhangSan","LiSi","WangWu"
}.Select(x => new Person(x)).ToList();

.ToList() 会在调用时将所有的 Person 马上实例化,这样在修改 firstPersonName 属性后,list 中的第一个元素的 Name 才会相应的修改。

参考

本文参考以下文章,在此致以诚挚谢意!

延迟执行和延迟计算 - LINQ to XML - .NET | Microsoft Learn

链式查询示例 (C#) - LINQ to XML - .NET | Microsoft Learn