ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

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

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

有趣的问题。就我个人而言,我更喜欢你上面的方法 - 更清楚发生了什么!
我来这里是为了寻找同一个问题的答案,并决定让未来的开发人员按照你在 OP 中所做的方式来做这件事一样简单、代码更少、更容易理解。
为什么要在 LINQ 中执行此操作?
这个问题问错了,唯一正确的答案是:不要使用LINQ修改数据源
我投票结束这个问题,因为这个问题的几乎所有答案都对新程序员对 LINQ 的理解有害。

A
Amirhossein Mehrvarzi

虽然您可以使用 ForEach 扩展方法,但如果您只想使用框架,您可以这样做

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

由于延迟评估,需要 ToList 来立即评估选择。


如果集合是一个 ObservableCollection 的说法,那么就地更改项目而不是创建一个新列表可能会很有用。
@desaivv 是的,这有点滥用语法,所以 Resharper 警告你。
恕我直言,这远没有简单的 foreach 循环那么富有表现力。 ToList() 令人困惑,因为它只用于强制评估,否则会被推迟。投影也令人困惑,因为它没有用于预期目的。相反,它用于迭代集合的元素并允许访问属性以便可以更新它。我心中唯一的问题是 foreach 循环是否可以从使用 Parallel.ForEach 的并行性中受益,但这是一个不同的问题。
这个答案是最糟糕的做法。永远不要这样做。
查看所有其他评论,了解为什么这是一个坏主意。您永远不应该使用 select 来执行副作用。 select 的目的是选择一个值,而不是模拟一个 for 每个循环。
Ε
Ε Г И І И О
collection.ToList().ForEach(c => c.PropertyToSet = value);

@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 }; });
与 Cameron MacFarland 的更新列表而不是创建新列表的答案相比,这具有优势。
哇,这个答案真的没用。创建一个新集合只是为了能够使用循环
这个答案是完全不正确的:它实际上并没有改变原始集合,它只是创建了一个你仍然需要分配到某个地方的新集合
S
Snake Eyes

我正在这样做

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

我认为这是最干净的方法。
这种方法确实有效,但它违反了 All() 扩展方法的意图,当其他人阅读代码时可能会造成混淆。
这种方法更好。使用 All 而不是使用每个循环
绝对比不必要地调用 ToList() 更喜欢这个,即使它对 All() 的用途有点误导。
如果您正在使用像 List<> 这样的集合,ForEach() 方法是完成此任务的一种更简单的方法。前 ForEach(c => { c.needsChange = value; })
L
Leandro Bardelli

我实际上找到了一个扩展方法,可以很好地做我想做的事

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

不错 :) Lomaxx,也许可以添加一个示例,以便窥视者可以在“动作”中看到它(繁荣!)。
如果您真的想避免 foreach 循环(无论出于何种原因),这是唯一有用的方法。
@Rango 您仍然没有避免 foreach 因为代码本身包含 foreach 循环
链接已损坏,现在可在以下位置找到: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
链接到 blogs.msdn.microsoft.com/ericlippert/2009/05/18/… 的 MSDN 博客 sourcejedi 很好地解释了为什么作者认为创建这种扩展方法是个坏主意。对我来说,作者最有说服力的理由是,与普通的 foreach 循环相比,使用这种扩展方法并不能真正节省很多击键。循环:foreach(Foo foo in foos){ statement involving foo; } 扩展方法:foos.ForEach((Foo foo)=>{ statement involving foo; });
P
Peter Mortensen

利用:

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

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


P
Peter Mortensen

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

当然,您可以使用 ForEach(顺便说一下,它不是 LINQ,而是 List<T> 上的扩展)来实现。但是这个 字面意思 无论如何都使用 foreach,但带有 lambda 表达式。除了这个 every LINQ 方法在内部迭代您的集合,例如使用 foreachfor,但它只是对客户端隐藏它。我不认为这更具可读性和可维护性(考虑在调试包含 lambda 表达式的方法时编辑代码)。

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

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


题外话:我同意,有很多 LINQ 被滥用的例子,人们要求“高性能 LINQ 链”的例子,做可以用单个循环完成的事情,等等。我很庆幸不使用 LINQ 是对我来说太根深蒂固了,通常不使用它。我看到人们使用 LINQ 链来执行单个操作,但没有意识到几乎每次使用 LINQ 命令时,您都会“在后台”创建另一个 for 循环。我觉得创建完成简单任务的不太冗长的方式是语法糖,而不是替代标准编码。
那么你的答案到底在哪里?
@JohnLord 答案很简单:在要修改集合的情况下不要使用 LINQ。
J
JaredPar

没有内置的扩展方法可以做到这一点。虽然定义一个是相当直接的。在帖子的底部是我定义的一个名为 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 或其他返回标量值的现有扩展方法有什么问题?
这与我想要的非常接近,但有点……涉及。我发布的博客文章具有类似的实现,但代码行数更少。
IterateHelper 似乎有点矫枉过正。不带索引的重载最终会做更多的额外工作(将回调转换为带索引的 lambda,保留一个从未使用过的计数)。我知道它是重用,但无论如何它只是使用 forloop 的一种解决方法,因此它应该是有效的。
@Cameron,IterateHelper 有两个目的。 1) 单一实现和 2) 允许在调用时与使用时抛出 ArgumentNullException。 C# 迭代器被延迟执行,帮助器防止在迭代期间抛出异常的奇怪行为。
@JaredPar:除非您没有使用迭代器。没有收益声明。
g
granadaCoder

我已经尝试了一些变体,并且我继续回到这个人的解决方案。

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

同样,这是其他人的解决方案。但是我已经将代码编译成一个小库,并且经常使用它。

我将在此处粘贴他的代码,以防他的网站(博客)在未来某个时候不复存在。 (没有什么比看到一个帖子说“这是您需要的确切答案”、点击和死 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; } );

您可以使用 Action<TSource> 而不是创建额外的委托。不过,在撰写本文时,这可能还不可用。
是的,那是当时的老派 DotNet。好的评论弗兰克。
网址已死! (此域名已过期)还好我在这里复制了代码! #patOnShoulder
检查值类型是有意义的,但使用约束(即where T: struct)在编译时捕获它可能会更好。
C
Community

不,LINQ 不支持大规模更新的方式。唯一更短的方法是使用 ForEach 扩展方法 - Why there is no ForEach extension method on IEnumerable?


A
AnthonyWJones

我的2便士:-

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

我喜欢这种想法,但不清楚代码在做什么
T
Tamas Czinege

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

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

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


P
PartTimeIndie

我写了一些扩展方法来帮助我解决这个问题。

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");
            }
        }

    }
}

B
Bill Forney

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

    /// <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;
    }

为什么“更新不支持值类型元素”?没有什么能干扰它!
那是特定于我正在从事的项目的。我想在大多数情况下这无关紧要。最近我对其进行了重新设计并将其重命名为 Run(...),删除了值类型并将其更改为返回 void 并删除了计数代码。
这或多或少是 List<T>.ForEach 的作用,但仅适用于所有 IEnumerable
现在回想起来,我想说只使用一个 foreach 循环。使用这样的东西的唯一好处是,如果您想将方法链接在一起并从函数返回可枚举以在执行操作后继续链接。否则这只是一个额外的方法调用,没有任何好处。
P
Peter Mortensen

您可以使用 Magiq,这是 LINQ 的批处理操作框架。


L
Leandro Bardelli

有些人认为这是一个评论,但对我来说是一个答案,因为做错事的正确方法是不去做。所以,这个问题的答案就在问题本身。

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


我同意,但 this answer 已经说过了。
S
Stormenet

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

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,否则它什么也不做。
江明哲

引用 Adi Lester 的回答 (https://stackoverflow.com/a/5755487/8917485)

我很喜欢这个答案,但是这个答案有一个错误。它只是更改新创建的列表中的值。它必须改为两行才能读取真正的更改列表。

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

这应该是一个评论。请等待,直到您可以正确发表评论。但是,事实并非如此。语句 collection.ToList().ForEach(c => c.PropertyToSet = value) 永久更改 collection 中的所有项目,它们碰巧以哪种方式被枚举并不重要。
这没有提供问题的答案。一旦你有足够的reputation,你就可以comment on any post;相反,provide answers that don't require clarification from the asker。 - From Review
V
Vishwa G

假设我们有如下数据,

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

如果我们想修改列表并将列表的现有值替换为修改后的值,则首先创建一个新的空列表,然后通过对每个列表项调用修改方法来循环数据列表,

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;

为什么要让可以在 O(n) 运行时运行的东西在 O(n^2) 或更糟的情况下执行? IM 不知道 linq 的具体工作方式,但我可以看到这至少是一个 ^2 问题的解决方案。