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


498

有没有办法使用LINQ执行以下操作?

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

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

我的用例是,我在博客文章上有很多评论,我想遍历博客文章上的每个评论,并将博客文章上的日期时间设置为+10小时。我可以用SQL做到这一点,但我想将其保留在业务层中。


14
有趣的问题。就个人而言,我更喜欢您如何做到这一点-更清楚了发生了什么!
noelicus '16

8
我来这里是为了寻找相同问题的答案,并决定它对于以后的开发人员来说就像在OP中一样进行操作是一样容易,更少的代码,并且更易于理解。
Casey Crookston

4
您为什么要在LINQ中这样做?
卡托

13
这个问题要求做错事情,唯一正确的答案是:不要使用LINQ修改数据源
Tim Schmelter

我投票结束这个问题是因为题外,因为几乎所有问题的答案都对新程序员对LINQ的理解产生积极的危害。
Tanveer Badar

Answers:


840

虽然可以使用ForEach扩展方法,但是如果只想使用框架,则可以执行

collection.Select(c => {c.PropertyToSet = value; return c;}).ToList();

ToList是必要的,以评估立即到期选择偷懒的评价


6
我对此表示赞同,因为它是一个非常不错的解决方案...我喜欢扩展方法的唯一原因是,它使您可以更清楚地了解正在发生的事情...但是,您的解决方案仍然很不错
lomaxx

9
如果ObservableCollection说收集是一种话语,那么更改项目而不是创建新列表可能会很有用。
卡梅隆·麦克法兰

7
@desaivv是的,这有点语法滥用,因此Resharper会警告您这一点。
Cameron MacFarland

46
恕我直言,这远没有一个简单的foreach循环那么容易表达。ToList()令人迷惑,因为它除了用于强制评估之外不用于其他任何用途,否则会推迟评估。投影也很混乱,因为它没有用于预期的目的。相反,它用于遍历集合的元素并允许访问属性,以便可以对其进行更新。我唯一想到的问题是,使用Parallel.ForEach并行处理是否可以使foreach循环受益,但这是一个不同的问题。
菲利普2014年

37
这个答案是最糟糕的做法。永远不要这样做。
埃里克·利珀特

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

36
@SanthoshKumar:使用collection.ToList().ForEach(c => { c.Property1ToSet = value1; c.Property2ToSet = value2; });
ΕГИІИО

@CameronMacFarland:当然不会,因为结构是不可变的。但如果你真的愿意,你可以这样做:collection.ToList().ForEach(c => { collection[collection.IndexOf(c)] = new <struct type>() { <propertyToSet> = value, <propertyToRetain> = c.Property2Retain }; });
ΕГИІИО

11
与Cameron MacFarland的答案相比,它具有就地更新列表而不是创建新列表的优势。
西蒙·图西

7
哇,这个答案真的没用。创建新的收藏集以便能够使用循环
Tim Schmelter

@SimonTewsi由于它是对象的集合,因此无论如何都应在适当的位置更新列表。该集合将是新的,但集合中的对象将相同。
克里斯(Chris)

70

我正在做这个

Collection.All(c => { c.needsChange = value; return true; });

我认为这是最干净的方法。
wcm 2013年

31
这种方法确实有效,但是它违反了All()扩展方法的意图,导致在其他人读取代码时可能造成混乱。
2015年

这种方法比较好。采用所有,而不是使用每个循环
UJS

2
绝对喜欢这样做,而不是不必要地调用ToList(),即使它对All()的用途有误导。
iupchris10

1
如果您正在使用具有该功能的集合List<>,则该ForEach()方法将是一种不太复杂的方法。前ForEach(c => { c.needsChange = value; })
丹在火光旁摆弄

27

我实际上找到了一种扩展方法,可以很好地满足我的需求

public static IEnumerable<T> ForEach<T>(
    this IEnumerable<T> source,
    Action<T> act)
{
    foreach (T element in source) act(element);
    return source;
}

4
不错:) Lomaxx,也许再加上一个例子,这样窥视者就可以在“动作”中看到它(繁荣!)。
Pure.Krome

2
如果您确实想避免foreach-loop(无论出于何种原因),这是唯一有用的方法。
蒂姆·施密特

@Rango,您仍然不能避免,foreach因为代码本身包含foreach循环
GoldBishop

@GoldBishop当然,该方法隐藏了循环。
蒂姆·施密特

1
链接已断开,现在可以在以下位置访问:codewrecks.com/blog/index.php/2008/08/13/…。还有一个博客评论链接到stackoverflow.com/questions/200574。反过来,最热门的问题评论链接到 blogs.msdn.microsoft.com/ericlippert/2009/05/18/…。使用MSDN重写答案可能会更简单(如果需要,您仍然可以相信第一个链接)。旁注:Rust具有类似功能,最终放弃并添加了等效功能:stackoverflow.com/a/50224248/799204
sourcejedi

14

采用:

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

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


7

没有内置的扩展方法可以执行此操作。尽管定义一个很简单。我在帖子的底部定义了一种称为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++;
    }
}

是否需要Iterate,Count,Sum,Avg或其他现有的扩展方法(返回标量值)有什么问题?
AnthonyWJones

2
这非常接近我想要的,但是有点..涉及。我发布的博客文章具有类似的实现方式,但代码行较少。
lomaxx

1
IterateHelper似乎过大了。不占用索引的重载最终会做很多额外的工作(将回调转换为需要索引的lambda,保留从未使用的计数)。我知道它是可重用的,但是无论如何仅使用forloop是一个解决方法,因此它应该是有效的。
卡梅伦·麦克法兰

2
@ Cameron,IterateHelper有两个用途。1)单一实现和2)允许在调用时与使用时引发ArgumentNullException。C#迭代器被延迟执行,使助手能够防止在迭代过程中引发异常的奇怪行为。
JaredPar

2
@JaredPar:除非您没有使用迭代器。没有收益声明。
卡梅伦·麦克法兰

7

尽管您专门要求使用LINQ解决方案,并且这个问题已经很老了,但我还是发布了非LINQ解决方案。这是因为LINQ(=语言集成查询)旨在用于集合查询。所有LINQ方法都不修改基础集合,它们只是返回一个新的集合(或更精确地说,是一个迭代器到一个新的集合)。因此,无论您做什么,例如Select都不会影响基础集合),您只会得到一个新的集合。

当然,您可以使用来实现ForEach(顺便说一句,它不是LINQ,而是扩展名List<T>)。但这实际上使用foreach了,但带有lambda表达式。除此之外,每个 LINQ方法都会在内部对您的集合进行迭代,例如通过使用foreachfor,但是它只是对客户端隐藏了它。我不认为这更具可读性和可维护性(想在调试包含lambda-expressions的方法时编辑代码)。

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

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


3
题外话:我同意,并且有很多LINQ实例被滥用,有人要求“高性能LINQ链”,以完成单个循环即可完成的工作,等等。我很高兴不使用LINQ是根深蒂固于我,通常不使用它。我看到人们使用LINQ链来执行单个动作,但几乎没有意识到每次使用LINQ命令时,您都是for在“后台” 创建另一个循环。我觉得创建简单的任务的冗长方式,而不是代替标准编码是一种语法糖。
ForeverZer0

6

我对此进行了一些尝试,然后继续讨论这个人的解决方案。

http://www.hookedonlinq.com/UpdateOperator.ashx

同样,这是别人的解决方案。但是我已经将代码编译到一个小的库中,并相当定期地使用它。

我将在此处粘贴他的代码,以免他的网站(blog)在将来的某个时候不再存在。(没有什么比看到显示“这里是您需要的确切答案”,点击和无效URL的帖子更糟糕的了。)

    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

URL已死!(此域名已过期)很好,我在这里复制了代码!#patOnShoulder
granadaCoder

1
检查值类型是有意义的,但是使用约束也许更好,即where T: struct在编译时捕获约束。
Groo


3

我写了一些扩展方法来帮助我。

namespace System.Linq
{
    /// <summary>
    /// Class to hold extension methods to Linq.
    /// </summary>
    public static class LinqExtensions
    {
        /// <summary>
        /// Changes all elements of IEnumerable by the change function
        /// </summary>
        /// <param name="enumerable">The enumerable where you want to change stuff</param>
        /// <param name="change">The way you want to change the stuff</param>
        /// <returns>An IEnumerable with all changes applied</returns>
        public static IEnumerable<T> Change<T>(this IEnumerable<T> enumerable, Func<T, T> change  )
        {
            ArgumentCheck.IsNullorWhiteSpace(enumerable, "enumerable");
            ArgumentCheck.IsNullorWhiteSpace(change, "change");

            foreach (var item in enumerable)
            {
                yield return change(item);
            }
        }

        /// <summary>
        /// Changes all elements of IEnumerable by the change function, that fullfill the where function
        /// </summary>
        /// <param name="enumerable">The enumerable where you want to change stuff</param>
        /// <param name="change">The way you want to change the stuff</param>
        /// <param name="where">The function to check where changes should be made</param>
        /// <returns>
        /// An IEnumerable with all changes applied
        /// </returns>
        public static IEnumerable<T> ChangeWhere<T>(this IEnumerable<T> enumerable, 
                                                    Func<T, T> change,
                                                    Func<T, bool> @where)
        {
            ArgumentCheck.IsNullorWhiteSpace(enumerable, "enumerable");
            ArgumentCheck.IsNullorWhiteSpace(change, "change");
            ArgumentCheck.IsNullorWhiteSpace(@where, "where");

            foreach (var item in enumerable)
            {
                if (@where(item))
                {
                    yield return change(item);
                }
                else
                {
                    yield return item;
                }
            }
        }

        /// <summary>
        /// Changes all elements of IEnumerable by the change function that do not fullfill the except function
        /// </summary>
        /// <param name="enumerable">The enumerable where you want to change stuff</param>
        /// <param name="change">The way you want to change the stuff</param>
        /// <param name="where">The function to check where changes should not be made</param>
        /// <returns>
        /// An IEnumerable with all changes applied
        /// </returns>
        public static IEnumerable<T> ChangeExcept<T>(this IEnumerable<T> enumerable,
                                                     Func<T, T> change,
                                                     Func<T, bool> @where)
        {
            ArgumentCheck.IsNullorWhiteSpace(enumerable, "enumerable");
            ArgumentCheck.IsNullorWhiteSpace(change, "change");
            ArgumentCheck.IsNullorWhiteSpace(@where, "where");

            foreach (var item in enumerable)
            {
                if (!@where(item))
                {
                    yield return change(item);
                }
                else
                {
                    yield return item;
                }
            }
        }

        /// <summary>
        /// Update all elements of IEnumerable by the update function (only works with reference types)
        /// </summary>
        /// <param name="enumerable">The enumerable where you want to change stuff</param>
        /// <param name="update">The way you want to change the stuff</param>
        /// <returns>
        /// The same enumerable you passed in
        /// </returns>
        public static IEnumerable<T> Update<T>(this IEnumerable<T> enumerable,
                                               Action<T> update) where T : class
        {
            ArgumentCheck.IsNullorWhiteSpace(enumerable, "enumerable");
            ArgumentCheck.IsNullorWhiteSpace(update, "update");
            foreach (var item in enumerable)
            {
                update(item);
            }
            return enumerable;
        }

        /// <summary>
        /// Update all elements of IEnumerable by the update function (only works with reference types)
        /// where the where function returns true
        /// </summary>
        /// <param name="enumerable">The enumerable where you want to change stuff</param>
        /// <param name="update">The way you want to change the stuff</param>
        /// <param name="where">The function to check where updates should be made</param>
        /// <returns>
        /// The same enumerable you passed in
        /// </returns>
        public static IEnumerable<T> UpdateWhere<T>(this IEnumerable<T> enumerable,
                                               Action<T> update, Func<T, bool> where) where T : class
        {
            ArgumentCheck.IsNullorWhiteSpace(enumerable, "enumerable");
            ArgumentCheck.IsNullorWhiteSpace(update, "update");
            foreach (var item in enumerable)
            {
                if (where(item))
                {
                    update(item);
                }
            }
            return enumerable;
        }

        /// <summary>
        /// Update all elements of IEnumerable by the update function (only works with reference types)
        /// Except the elements from the where function
        /// </summary>
        /// <param name="enumerable">The enumerable where you want to change stuff</param>
        /// <param name="update">The way you want to change the stuff</param>
        /// <param name="where">The function to check where changes should not be made</param>
        /// <returns>
        /// The same enumerable you passed in
        /// </returns>
        public static IEnumerable<T> UpdateExcept<T>(this IEnumerable<T> enumerable,
                                               Action<T> update, Func<T, bool> where) where T : class
        {
            ArgumentCheck.IsNullorWhiteSpace(enumerable, "enumerable");
            ArgumentCheck.IsNullorWhiteSpace(update, "update");

            foreach (var item in enumerable)
            {
                if (!where(item))
                {
                    update(item);
                }
            }
            return enumerable;
        }
    }
}

我正在这样使用它:

        List<int> exampleList = new List<int>()
            {
                1, 2 , 3
            };

        //2 , 3 , 4
        var updated1 = exampleList.Change(x => x + 1);

        //10, 2, 3
        var updated2 = exampleList
            .ChangeWhere(   changeItem => changeItem * 10,          // change you want to make
                            conditionItem => conditionItem < 2);    // where you want to make the change

        //1, 0, 0
        var updated3 = exampleList
            .ChangeExcept(changeItem => 0,                          //Change elements to 0
                          conditionItem => conditionItem == 1);     //everywhere but where element is 1

供参考的参数检查:

/// <summary>
/// Class for doing argument checks
/// </summary>
public static class ArgumentCheck
{


    /// <summary>
    /// Checks if a value is string or any other object if it is string
    /// it checks for nullorwhitespace otherwhise it checks for null only
    /// </summary>
    /// <typeparam name="T">Type of the item you want to check</typeparam>
    /// <param name="item">The item you want to check</param>
    /// <param name="nameOfTheArgument">Name of the argument</param>
    public static void IsNullorWhiteSpace<T>(T item, string nameOfTheArgument = "")
    {

        Type type = typeof(T);
        if (type == typeof(string) ||
            type == typeof(String))
        {
            if (string.IsNullOrWhiteSpace(item as string))
            {
                throw new ArgumentException(nameOfTheArgument + " is null or Whitespace");
            }
        }
        else
        {
            if (item == null)
            {
                throw new ArgumentException(nameOfTheArgument + " is null");
            }
        }

    }
}

2

我的2便士:-

 collection.Count(v => (v.PropertyToUpdate = newValue) == null);

7
我喜欢这种想法,但是并不清楚代码在做什么
lomaxx

2

您可以使用LINQ将您的集合转换为数组,然后调用Array.ForEach():

Array.ForEach(MyCollection.ToArray(), item=>item.DoSomeStuff());

显然,这不适用于结构或内置类型(如整数或字符串)的集合。


1

这是我使用的扩展方法...

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

        var count = 0;
        foreach (var element in source)
        {
            action(element);
            count++;
        }
        return count;
    }

为什么“更新不支持值类型元素”?没有任何干扰!
abatishchev

那是我正在从事的项目所特有的。我想在大多数情况下都没有关系。最近,我对其进行了重新设计,并将其重命名为Run(...),删除了值类型的东西,并将其更改为返回void并删除了计数代码。
Bill Forney

这或多或少List<T>.ForEach也起到了作用,但仅仅是为了所有人IEnumerable
HimBromBeere

现在回首一下,我会说只使用一个foreach循环。使用这样的东西的唯一好处是,如果您想将方法链接在一起,并从函数返回可枚举,以便在执行操作后继续进行链接。否则,这只是一个额外的方法调用,没有好处。
比尔·佛尼

0

我假设您想更改查询中的值,以便可以为其编写函数

void DoStuff()
{
    Func<string, Foo, bool> test = (y, x) => { x.Bar = y; return true; };
    List<Foo> mylist = new List<Foo>();
    var v = from x in mylist
            where test("value", x)
            select x;
}

class Foo
{
    string Bar { get; set; }
}

但是,如果这就是您的意思,请不要犹豫。


这将朝着正确的方向发展,将需要一些枚举v,否则将无济于事。
AnthonyWJones


-3

假设我们有如下数据,

var items = new List<string>({"123", "456", "789"});
// Like 123 value get updated to 123ABC ..

如果要修改列表并将列表的现有值替换为修改后的值,则首先创建一个新的空列表,然后通过在每个列表项上调用Modify方法来遍历数据列表,

var modifiedItemsList = new List<string>();

items.ForEach(i => {
  var modifiedValue = ModifyingMethod(i);
  modifiedItemsList.Add(items.AsEnumerable().Where(w => w == i).Select(x => modifiedValue).ToList().FirstOrDefault()?.ToString()) 
});
// assign back the modified list
items = modifiedItemsList;

2
为什么要制作可以在O(n)运行时中运行的东西,或者在O(n ^ 2)中执行或更糟的事情?我不知道linq的细节如何工作,但我可以看到这至少是n问题的an ^ 2解决方案。
Fallenreaper
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.