如何通过 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