使用LINQ更新集合中的所有对象

628

有没有使用LINQ实现以下操作的方法?

foreach (var c in collection)
{
    c.PropertyToSet = value;
}

为了澄清,我想遍历集合中的每个对象,然后更新每个对象上的某个属性。

我的使用场景是在博客文章上有许多评论,并且我想遍历每个评论并将博客文章的日期时间设置为+10小时。我可以在SQL中完成,但我想将其保留在业务层。


18
有趣的问题。就个人而言,我更喜欢您上面的表述——更清楚地说明了正在发生的事情! - noelicus
13
我来到这里是寻找同一个问题的答案,我认为像你在原始帖子中所做的那样做就可以了,这样做更容易,代码更少,并且对未来的开发者更易于理解。 - Casey Crookston
18
这个问题所要求的并不正确,唯一正确的答案是:不要使用LINQ来修改数据源。 - Tim Schmelter
4
我投票关闭此问题,因为几乎所有回答都会对新程序员理解LINQ产生积极的负面影响。 - Tanveer Badar
5
哇,这里有很多赞数很高的答案,但都是做这件事情的可怕方法。只需使用foreach循环,它更易读且没有奇怪的副作用。 - DavidG
显示剩余3条评论
18个回答

993

虽然您可以使用 ForEach 扩展方法,但如果您想仅使用框架,则可以执行以下操作

collection.Select(c => {c.PropertyToSet = value; return c;}).ToList();
< p > ToList 是必须的,因为由于“惰性求值”,需要立即计算选择结果。 < /p >

11
如果集合是一个 ObservableCollection 的话,那么在原地更改项目而不是创建新列表可以很有用。 - Cameron MacFarland
10
@desaivv 嗯,这有点语法滥用,所以Resharper正在警告您。 - Cameron MacFarland
65
我的看法是,这远不如一个简单的 foreach 循环来表达。使用 ToList() 很令人困惑,因为它没有被用于除了强制评估之外的其他任何目的。投影也很令人困惑,因为它没有被用于其预期目的;相反,它被用于遍历集合元素并允许访问属性以便可以更新它。我唯一心中存疑的问题是 foreach 循环是否可以通过 Parallel.ForEach 实现并行化,但那是另一个问题。 - Philippe
129
这个答案是最不好的实践。永远不要这样做。 - Eric Lippert
19
请看其他评论了解为什么这是一个不好的想法。你永远不应该使用select来执行副作用。select的目的是选择一个值,而不是模拟一个for each循环。 - Eric Lippert
显示剩余30条评论

456
collection.ToList().ForEach(c => c.PropertyToSet = value);

51
使用 collection.ToList().ForEach(c => { c.Property1ToSet = value1; c.Property2ToSet = value2; }); - Ε Г И І И О
12
相比于Cameron MacFarland的回答,这种方法的优势在于可以直接更新列表,而不是创建一个新的列表。 - Simon Elms
13
哇,这个答案真的没什么用。只是为了使用循环而创建一个新集合。 - Tim Schmelter
3
这个回答是不正确的:它实际上并没有改变原始集合,而只是创建了一个新集合,你仍然需要将其分配到某个地方。 - Allie
4
@Allie:你的批评是正确的,但只有在集合中的元素是值类型(即结构体)时才会出现这个问题。如果c是引用类型,则以这种方式设置PropertyToSet的行为符合预期。请参见https://pastebin.com/rtG2i1KX。 - Brian
显示剩余6条评论

83
我正在这样做。
Collection.All(c => { c.needsChange = value; return true; });

我认为这是最干净的方法。 - wcm
41
这种方法确实可行,但违反了All()扩展方法的本意,会导致其他人阅读代码时出现潜在的混淆。 - Tom Baxter
2
绝对比不必要地调用ToList()更好,即使它在使用All()方面有点误导。 - iupchris10
1
如果您正在使用像List<>这样的集合,那么ForEach()方法是一种更不神秘的实现方式。例如:ForEach(c => { c.needsChange = value; }) - Dan Is Fiddling By Firelight
All和Any用于条件检查。如果需要同时更新多个列,则最好使用MyList.ForEach(q => { q.StartDate.AddHours(8); q.EndDate.AddHours(8); });。 - TPG
显示剩余4条评论

31
我实际上找到了一个扩展方法,可以很好地完成我想要的功能。
public static IEnumerable<T> ForEach<T>(
    this IEnumerable<T> source,
    Action<T> act)
{
    foreach (T element in source) act(element);
    return source;
}

4
好的 :) Lomaxx,也许可以添加一个例子,这样人们就可以看到它在“行动”中的效果(boom tish!)。 - Pure.Krome
2
如果你真的想避免 foreach 循环(无论出于何种原因),那么这是唯一有用的方法。 - Tim Schmelter
1
链接已经失效,现在可以在以下网址找到:http://www.codewrecks.com/blog/index.php/2008/08/13/linq-foreach-for-ienumerablet/。还有一篇博客评论链接到https://dev59.com/GHVC5IYBdhLWcg3wtzyt。而顶部问题的评论链接到https://blogs.msdn.microsoft.com/ericlippert/2009/05/18/foreach-vs-foreach/。也许使用MSDN重新编写答案会更简单(如果您愿意,仍然可以给第一个链接信用)。附注:Rust具有类似的功能,并最终添加了等效函数:https://dev59.com/n1wY5IYBdhLWcg3wNFhY#50224248 - sourcejedi
1
sourcejedi链接的MSDN博客(https://blogs.msdn.microsoft.com/ericlippert/2009/05/18/foreach-vs-foreach/)对为什么作者认为创建这样一个扩展方法是个坏主意有很好的解释。对我来说,作者最有说服力的原因是,与普通的foreach循环相比,使用这样的扩展方法并不能真正节省多少击键。循环:`foreach(Foo foo in foos){ statement involving foo; }扩展方法:foos.ForEach((Foo foo)=>{ statement involving foo; });` - Simon Elms
请注意,这是一个不好的想法,可能会导致意外行为。如果您要使用这样的方法,它应该是一个void方法。问题在于,这实际上是一个“实现集合”的方法,假装成一个“添加更多惰性处理”的方法。这是一个假装成惰性的非惰性过程。 - Dave Cousineau
显示剩余3条评论

26

使用:

ListOfStuff.Where(w => w.Thing == value).ToList().ForEach(f => f.OtherThing = vauleForNewOtherThing);

我不确定这是否过度使用了LINQ,但当想要针对特定条件更新列表中的特定项时,这对我很有帮助。


23

虽然你明确要求了一个LINQ解决方案,而且这个问题很旧了,但我发布了一个非LINQ解决方案。这是因为LINQ (= language integrated query) 用于集合查询。所有LINQ方法都不修改基础集合,它们只返回一个新的(或更精确地说是新集合的迭代器)。因此,无论你使用什么方法,例如Select,都不会影响基础集合,你只会得到一个新集合。

当然,你也可以使用ForEach(顺便说一下,这不是LINQ,而是对List<T>的扩展)。但是这实际上仍然使用了foreach,只是用了lambda表达式。除此之外,每个LINQ方法在内部迭代集合,例如使用foreachfor,但它只是从客户端隐藏了这一点。我认为这并不更可读或易于维护(考虑在调试包含lambda表达式的方法时编辑代码)。

话虽如此,你不应该使用LINQ来修改你的集合中的项目。更好的方式是你已经在问题中提供的解决方案。通过经典循环,你可以轻松地迭代你的集合并更新它的项目。事实上,那些依赖于List.ForEach的解决方案并没有什么不同,但从我的角度来看更难以阅读。

因此,在您想要更新集合元素的情况下,不应该使用LINQ。


4
离题:我同意,有很多滥用LINQ的情况,例如人们请求“高性能LINQ链”来完成可以用单个循环完成的任务等等。我感谢自己没有过度依赖LINQ并且通常不使用它。我看到人们使用LINQ链来执行单个操作,却没有意识到每次使用LINQ命令都会在“幕后”创建另一个for循环。我认为它是一种语法糖,用于创建更简洁的方式来完成简单的任务,而不是替代标准编码。 - ForeverZer0
1
那么你的答案到底在所有这些内容中的哪里呢? - John Lord
1
@JohnLord,简单地说:在你想修改集合的情况下,请不要使用LINQ。 - MakePeaceGreatAgain

10

没有内置的扩展方法来完成这个任务。虽然定义一个扩展方法相当简单。在本文底部有一个我定义的名为Iterate的方法。可以像下面这样使用:

collection.Iterate(c => { c.PropertyToSet = value;} );

迭代源代码

public static void Iterate<T>(this IEnumerable<T> enumerable, Action<T> callback)
{
    if (enumerable == null)
    {
        throw new ArgumentNullException("enumerable");
    }

    IterateHelper(enumerable, (x, i) => callback(x));
}

public static void Iterate<T>(this IEnumerable<T> enumerable, Action<T,int> callback)
{
    if (enumerable == null)
    {
        throw new ArgumentNullException("enumerable");
    }

    IterateHelper(enumerable, callback);
}

private static void IterateHelper<T>(this IEnumerable<T> enumerable, Action<T,int> callback)
{
    int count = 0;
    foreach (var cur in enumerable)
    {
        callback(cur, count);
        count++;
    }
}

迭代是否必要?Count、Sum、Avg或其他返回标量值的现有扩展方法有什么问题吗? - AnthonyWJones
2
这很接近我想要的,但有点复杂。我发布的博客文章有一个类似的实现,但代码行数更少。 - lomaxx
1
IterateHelper似乎有些过度设计。不带索引的重载最终会做更多的额外工作(将回调转换为需要索引的lambda表达式,保留一个从未使用的计数器)。我理解它是为了重用,但这只是绕过使用for循环的方法,所以应该是高效的。 - Cameron MacFarland
2
@Cameron,IterateHelper有两个作用。 1)单一实现和2)允许在调用时抛出ArgumentNullException,而不是在使用时。 C#迭代器是延迟执行的,具有辅助程序可以防止在迭代过程中抛出异常的奇怪行为。 - JaredPar
2
@JaredPar:除非您不使用迭代器。 没有yield语句。 - Cameron MacFarland
@Cameron,Doh,这是我拥有的扩展方法集合的一部分,它是唯一一个不使用迭代器的方法。无论哪种情况,额外的开销都是可以忽略不计的。 - JaredPar

7
我尝试了几种变化,但我一直回到这个人的解决方案。 http://www.hookedonlinq.com/UpdateOperator.ashx 再次说明,这是别人的解决方案。但我已经将代码编译成一个小型库,并且经常使用它。
为了防止这位博主的网站在未来某个时候停用,我将其代码粘贴在此处。(没有什么比看到帖子说“这是你需要的确切答案”,点击后发现链接无效更糟糕了。)
    public static class UpdateExtensions {

    public delegate void Func<TArg0>(TArg0 element);

    /// <summary>
    /// Executes an Update statement block on all elements in an IEnumerable<T> sequence.
    /// </summary>
    /// <typeparam name="TSource">The source element type.</typeparam>
    /// <param name="source">The source sequence.</param>
    /// <param name="update">The update statement to execute for each element.</param>
    /// <returns>The numer of records affected.</returns>
    public static int Update<TSource>(this IEnumerable<TSource> source, Func<TSource> update)
    {
        if (source == null) throw new ArgumentNullException("source");
        if (update == null) throw new ArgumentNullException("update");
        if (typeof(TSource).IsValueType)
            throw new NotSupportedException("value type elements are not supported by update.");

        int count = 0;
        foreach (TSource element in source)
        {
            update(element);
            count++;
        }
        return count;
    }
}



int count = drawingObjects
        .Where(d => d.IsSelected && d.Color == Colors.Blue)
        .Update(e => { e.Color = Color.Red; e.Selected = false; } );

1
您可以使用 Action<TSource> 而不是创建额外的委托。尽管在撰写本文时可能还没有这个选项。 - Frank J
是的,那时候是老派的DotNet。好评论,弗兰克。 - granadaCoder
该网址已失效!(此域名已过期)幸好我在这里复制了代码!#为自己点赞 - granadaCoder
1
检查值类型是有意义的,但也许最好使用约束条件,即 where T: struct,在编译时捕获它。 - vgru

6

有些人认为这是一条评论,但对我而言,它是一个答案,因为做错事情的正确方法就是不去做。因此,这个问题的答案就在问题本身。

不要使用LINQ来修改数据。使用循环。


我同意,但是这个答案已经说过了。 - Gert Arnold
@Leandro 为什么?请解释一下。 - undefined

5

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