LINQ:选择一个对象并更改一些属性而不创建新对象

271
我想要更改LINQ查询结果对象的某些属性,而不需要创建一个新对象并手动设置每个属性。这可能吗?
例如:
var list = from something in someList
           select x // but change one property

1
抱歉!这是正确的地址:https://robvolk.com/linq-select-an-object-but-change-some-properties-without-creating-a-new-object-af4072738e33 - Rob Volk
1
参见:https://stackoverflow.com/questions/47836019/linq-set-a-property-while-doing-a-projection?noredirect=1#47836189 - Revious
2
虽然可以这样做,正如答案所示,但请注意这违反了LINQ的本质。LINQ方法不应该引起副作用,因此这样做与最小惊讶原则不符。按照LINQ的原则,您应该获取对象,然后修改它们。 - Gert Arnold
14个回答

465

我不确定查询语法是什么。但这里有一个扩展的LINQ表达式示例。

var query = someList.Select(x => { x.SomeProp = "foo"; return x; })

这个方法使用匿名方法而非表达式,其允许你在一个Lambda中使用多个语句。因此,你可以将设置属性和返回对象的两个操作合并到这个比较简洁的方法中。


6
@Rob,不容易。使它起作用的语法…最多也很难读懂。变量查询是从列表中选择并返回一个函数的结果,该函数将it.x设为42然后返回it本身。 - JaredPar
51
我遇到了“Lambda 表达式带有语句体,无法转换为表达式树”的错误。这不是针对 LINQ to SQL 的,你有什么建议吗? - surya
14
尝试使用 Linq to SQL 时,我得到了与 @surya 相同的消息。在那之前添加 ToList() 似乎能解决问题。不过我不确定为什么你会收到那个消息。如果是 Entity Framework,则也不能用这种方法。 - Thomas Glaser
6
当你调用ToList()时,实际上是将数据带回内存中。这样就不再是Linq to SQL了。 - Oak
8
根据Eric Lippert的说法,这种解决方案是“低效、令人困惑、难以维护、不必要、浪费资源、依赖于查询操作符的实现细节、不能推广到非LINQ-to-objects场景,并且滥用了一个旨在回答问题而非执行副作用的操作符。”请参见他对此答案的评论。 - Luca Cremonesi
显示剩余5条评论

69

如果您只想更新所有元素上的属性,则

someList.All(x => { x.SomeProp = "foo"; return true; })

3
在EF(Entity Framework)中,针对IEnumerable中所有对象替换一个属性,接受的答案对我有效。以下是我使用的可行代码:var myList = _db.MyObjects.Where(o => o.MyProp == "bar").AsEnumerable().Select(x => { x.SomeProp = "foo"; return x; }); - firepol

36

我更喜欢这个。它可以与其他linq命令结合使用。

from item in list
let xyz = item.PropertyToChange = calcValue()
select item

3
表达式树不能包含赋值操作。 - Daniel
@Daniel 在发帖之前,你有尝试过你的假设吗? - Jan Zahradník
是的,我实际上尝试了你的解决方案。这里是关于将此功能添加到C#的最新提议:https://github.com/dotnet/csharplang/discussions/4727 - Daniel

33

没有任何的 LINQ 魔法会阻止你这样做。不过不要使用投影,因为那会返回一个匿名类型。

User u = UserCollection.FirstOrDefault(u => u.Id == 1);
u.FirstName = "Bob"

那会修改真正的对象,以及:

foreach (User u in UserCollection.Where(u => u.Id > 10)
{
    u.Property = SomeValue;
}

我喜欢这个,我的问题是在第一个例子中,如果列表中找不到一些u > 10怎么办?我添加了一个空值检查,似乎可以工作。此外,我将左侧命名为u到v。+1虽然。非常简洁。 - One-One

15
如果您想使用Where子句更新项目,使用.Where(...)将会截断结果,如果您这样做的话:
list = list.Where(n => n.Id == ID).Select(n => { n.Property = ""; return n; }).ToList();
您可以像下面这样更新列表中的特定项目:
list = list.Select(n => { if (n.Id == ID) { n.Property = ""; } return n; }).ToList();

即使您没有进行任何更改,也请始终返回项目。这样它将保留在列表中。


12

使用标准查询运算符是不可能的 - 它是语言集成查询,而不是语言集成更新。但你可以在扩展方法中隐藏你的更新操作。

public static class UpdateExtension
{
    public static IEnumerable<Car> ChangeColorTo(
       this IEnumerable<Car> cars, Color color)
    {
       foreach (Car car in cars)
       {
          car.Color = color;
          yield return car;
       }
    }
}

现在你可以按照以下方式使用它。

cars.Where(car => car.Color == Color.Blue).ChangeColorTo(Color.Red);

1
不是你想要更新基本集合。我们希望更新结果集合属性。 - Michael Brennt
4
LINQ取代了结构化查询语言(SQL),但仍具备更新、删除和插入的能力。因此,我认为语义并不是阻止这个功能的原因。 - KyleMit

2
我们经常遇到这种情况,希望在列表中包含索引值和首尾指示符,但不创建新对象。这样可以在不修改现有类的情况下了解项目在列表、枚举等中的位置,而无需知道您是否处于列表中的第一个或最后一个项目。
foreach (Item item in this.Items
    .Select((x, i) => {
    x.ListIndex = i;
    x.IsFirst = i == 0;
    x.IsLast = i == this.Items.Count-1;
    return x;
}))

您可以使用以下方法简单地扩展任何类:

public abstract class IteratorExtender {
    public int ListIndex { get; set; }
    public bool IsFirst { get; set; } 
    public bool IsLast { get; set; } 
}

public class Item : IteratorExtender {}

1

1

由于我在这里没有找到我认为最佳解决方案的答案,因此我要分享我的做法:

使用“Select”来修改数据是可能的,但需要一个技巧。然而,“Select”并不是为此而设计的。只有当与“ToList”一起使用时,“Select”才执行修改操作,因为Linq在需要数据之前不会执行操作。 无论如何,最好的解决方案是使用“foreach”。 在以下代码中,您可以看到:

    class Person
    {
        public int Age;
    }

    class Program
    {
        private static void Main(string[] args)
        {
            var persons = new List<Person>(new[] {new Person {Age = 20}, new Person {Age = 22}});
            PrintPersons(persons);

            //this doesn't work:
            persons.Select(p =>
            {
                p.Age++;
                return p;
            });
            PrintPersons(persons);

            //with "ToList" it works
            persons.Select(p =>
            {
                p.Age++;
                return p;
            }).ToList();
            PrintPersons(persons);

            //This is the best solution
            persons.ForEach(p =>
            {
                p.Age++;
            });
            PrintPersons(persons);
            Console.ReadLine();
        }

        private static void PrintPersons(List<Person> persons)
        {
            Console.WriteLine("================");
            foreach (var person in persons)
            {
                Console.WriteLine("Age: {0}", person.Age);
            ;
            }
        }
    }

在“foreach”之前,您还可以进行Linq选择...

0
使用ForEach
var list = from something in someList;
list.ForEach(x => x.Property = value);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接