如何通过 Linq 更新列表中的项目以保持列表的其余部分不变
How to update an item in a list keeping the rest of the list intact through Linq
假设我们有一个列表,
Dog d1 = new Dog("Fluffy", "1");
Dog d2 = new Dog("Rex", "2");
Dog d3 = new Dog("Luna", "3");
Dog d4 = new Dog("Willie", "4");
List<Dog> AllDogs = new List<Dog>()
AllDogs.Add(d1);
AllDogs.Add(d2);
AllDogs.Add(d3);
AllDogs.Add(d4);
如果我想更新 d4,从它的名字 willie 到 wolly。我想要 return 一个包含所有狗的列表以及 d4 的更新。
我正在尝试这样的事情:
var dog = AllDogs.Where(d => d.Id == "2").FirstOrDefault();
if (dog != null) { dog.Name = "some value"; }
但这 return 只是更新的项目,我想要包含更新项目的整个列表。
AllDogs
将包含更新的对象,因此您需要做的就是将其设为 public 属性(如果您从另一个 class 访问它)或私有 class 变量(如果您只是使用相同的 class 访问它)。
行:
var dog = AllDogs.Where(d => d.Id == "2").FirstOrDefault();
returns 对列表中对象的引用,因此当您更新它时:
if (dog != null) { dog.Name = "some value"; }
您正在列表中更新它。
这是实现它的实用方法。这将创建一个包含新狗的新列表。这适用于任何 IEnumerable<Dog>
var dogs = AllDogs.Select(s =>
new Dog() { Id = s.Id, Name = (s.Id == "2" ? "some value" : s.Name) });
这是一种在不创建任何新对象的情况下就地编辑列表的方法。这仅适用于 List<Dog>
AllDogs.ForEach(f => f.Name = (f.Id == "2" ? "some value" : f.Name));
您可以使用 List.ConvertAll,它的作用与 Linq Select
相同,然后是 ToList
(或一些复杂的 Aggregate
)。
它将创建一个全新的列表,其中包含对狗对象的相同引用以及在此过程中所做的修改:
// List.ConvertAll
var newAllDogs = allDogs.ConvertAll (dog => {
if (dog.Id == 2)
dog.Name = "some value";
return dog;
});
// Linq version
var newAllDogs = allDogs.Select (dog => {
if (dog.Id == 2)
dog.Name = "some value";
return dog;
}).ToList ();
但是做这种 "side-effect mutation"(或 List.ForEach 甚至更多 "side-effecty")你应该几乎坚持使用 foreach 循环的经典 "imperative" 代码或 List.Find :
// foreach
foreach (var dog in allDogs)
if (dog.Id == 2)
{
dog.Name = "some value";
break;
}
// List.Find
var theDog = allDogs.Find (dog => dog.Id == 2);
theDog?.Name = "some value"; // using C#6 null conditional operator
// without null conditional operator
if (theDog != null)
theDog.Name = "some value";
// in both cases allDogs will contain the updated dog
假设我们有一个列表,
Dog d1 = new Dog("Fluffy", "1");
Dog d2 = new Dog("Rex", "2");
Dog d3 = new Dog("Luna", "3");
Dog d4 = new Dog("Willie", "4");
List<Dog> AllDogs = new List<Dog>()
AllDogs.Add(d1);
AllDogs.Add(d2);
AllDogs.Add(d3);
AllDogs.Add(d4);
如果我想更新 d4,从它的名字 willie 到 wolly。我想要 return 一个包含所有狗的列表以及 d4 的更新。
我正在尝试这样的事情:
var dog = AllDogs.Where(d => d.Id == "2").FirstOrDefault();
if (dog != null) { dog.Name = "some value"; }
但这 return 只是更新的项目,我想要包含更新项目的整个列表。
AllDogs
将包含更新的对象,因此您需要做的就是将其设为 public 属性(如果您从另一个 class 访问它)或私有 class 变量(如果您只是使用相同的 class 访问它)。
行:
var dog = AllDogs.Where(d => d.Id == "2").FirstOrDefault();
returns 对列表中对象的引用,因此当您更新它时:
if (dog != null) { dog.Name = "some value"; }
您正在列表中更新它。
这是实现它的实用方法。这将创建一个包含新狗的新列表。这适用于任何 IEnumerable<Dog>
var dogs = AllDogs.Select(s =>
new Dog() { Id = s.Id, Name = (s.Id == "2" ? "some value" : s.Name) });
这是一种在不创建任何新对象的情况下就地编辑列表的方法。这仅适用于 List<Dog>
AllDogs.ForEach(f => f.Name = (f.Id == "2" ? "some value" : f.Name));
您可以使用 List.ConvertAll,它的作用与 Linq Select
相同,然后是 ToList
(或一些复杂的 Aggregate
)。
它将创建一个全新的列表,其中包含对狗对象的相同引用以及在此过程中所做的修改:
// List.ConvertAll
var newAllDogs = allDogs.ConvertAll (dog => {
if (dog.Id == 2)
dog.Name = "some value";
return dog;
});
// Linq version
var newAllDogs = allDogs.Select (dog => {
if (dog.Id == 2)
dog.Name = "some value";
return dog;
}).ToList ();
但是做这种 "side-effect mutation"(或 List.ForEach 甚至更多 "side-effecty")你应该几乎坚持使用 foreach 循环的经典 "imperative" 代码或 List.Find :
// foreach
foreach (var dog in allDogs)
if (dog.Id == 2)
{
dog.Name = "some value";
break;
}
// List.Find
var theDog = allDogs.Find (dog => dog.Id == 2);
theDog?.Name = "some value"; // using C#6 null conditional operator
// without null conditional operator
if (theDog != null)
theDog.Name = "some value";
// in both cases allDogs will contain the updated dog