ChatGPT解决这个技术问题 Extra ChatGPT

Update all objects in a collection using LINQ

Is there a way to do the following using LINQ?

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

To clarify, I want to iterate through each object in a collection and then update a property on each object.

My use case is I have a bunch of comments on a blog post, and I want to iterate through each comment on a blog post and set the datetime on the blog post to be +10 hours. I could do it in SQL, but I want to keep it in the business layer.

Interesting question. Personally I prefer how you've got it above - far clearer what's going on!
I came here looking for an answer to the same question, and decided that it was just as easy, less code, and easier to understand for future developers to just do it the way you did in you OP.
Why would you want to do it in LINQ?
This question asks for the wrong thing, the only correct answer is: don't use LINQ to modify the datasource
I'm voting to close this question as off-topic because almost all the answers to this question are actively harmful to new programmers' understanding of LINQ.

A
Amirhossein Mehrvarzi

While you can use a ForEach extension method, if you want to use just the framework you can do

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

The ToList is needed in order to evaluate the select immediately due to lazy evaluation.


If collection was an ObservableCollection say, then changing items in place rather than creating a new list can be useful.
@desaivv yeah this is a bit of a syntax abuse, so Resharper is warning you about this.
IMHO, this is far less expressive than a simple foreach loop. The ToList() is confusing because it is not used for anything but forcing evaluation that would otherwise be deferred. The projection is also confusing because it's not used for its intended purpose; rather, it's used to iterate over the elements of the collection and allow access to a property so that it can be updated. The only question in my mind would be whether or not the foreach loop could benefit from parallelism using Parallel.ForEach, but that's a different question.
This answer is a worst practice. Never do this.
See all the other comments for reasons why this is a bad idea. You should never use a select to execute a side effect. The purpose of a select is to select a value, not to emulate a for each loop.
Ε
Ε Г И І И О
collection.ToList().ForEach(c => c.PropertyToSet = value);

@SanthoshKumar: Use collection.ToList().ForEach(c => { c.Property1ToSet = value1; c.Property2ToSet = value2; });
@CameronMacFarland: Of course it won't since structs are immutable. But if you really want, you can do this: collection.ToList().ForEach(c => { collection[collection.IndexOf(c)] = new <struct type>() { <propertyToSet> = value, <propertyToRetain> = c.Property2Retain }; });
This has the advantage over Cameron MacFarland's answer of updating the list in place, rather than creating a new list.
Wow, this answer is really not useful. Creating a new collection just to be able to use a loop
this answer is simply incorrect: it does not actually change the original collection, it just creates a new one that you still need to assign somewhere
S
Snake Eyes

I am doing this

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

I think this is the cleanest way to do it.
This approach certainly works but it violates the intent of the All() extension method, leading to potential confusion when someone else reads the code.
This approach is better .Using All instead of using each loop
Definitely prefer this over calling ToList() unnecessarily, even if it's a little misleading about what it's using All() for.
If you're using a collection like List<> that has it theForEach() method is a much less cryptic way to accomplish this. ex ForEach(c => { c.needsChange = value; })
L
Leandro Bardelli

I actually found an extension method that will do what I want nicely

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

nice :) Lomaxx, maybe add an example so peeps can see it in 'action' (boom tish!).
This is the only useful approach if you really want to avoid a foreach-loop (for whatever reason).
@Rango which you are still NOT avoiding foreach as the code itself contains the foreach loop
The link is broken, it is now available at: codewrecks.com/blog/index.php/2008/08/13/… . There's also a blog comment that links to stackoverflow.com/questions/200574 . In turn, the top question comment links to blogs.msdn.microsoft.com/ericlippert/2009/05/18/… . Perhaps the answer would be simpler re-written using the MSDN (you could still credit the first link if you wanted). Sidenote: Rust has similar features, and eventually gave in and added the equivalent function: stackoverflow.com/a/50224248/799204
The MSDN blog sourcejedi linked to, at blogs.msdn.microsoft.com/ericlippert/2009/05/18/…, has a good explanation of why the author thought it was a bad idea to create such an extension method. The author's most convincing reason, to me, is that using such an extension method doesn't really save many keystrokes compared to a plain foreach loop. Loop: foreach(Foo foo in foos){ statement involving foo; } Extension method: foos.ForEach((Foo foo)=>{ statement involving foo; });
P
Peter Mortensen

Use:

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

I am not sure if this is overusing LINQ or not, but it has worked for me when wanting to update a specific items in the list for a specific condition.


P
Peter Mortensen

Although you specifically asked for a LINQ solution and this question is quite old I post a non-LINQ-solution. This is because LINQ (= language integrated query) is meant to be used for queries on collections. All LINQ-methods don’t modify the underlying collection, they just return a new one (or more precise an iterator to a new collection). Thus whatever you do e.g. with a Select doesn’t effect the underlying collection, you simply get a new one.

Of course you could do it with a ForEach (which isn't LINQ, by the way, but an extension on List<T>). But this literally uses foreach anyway, but with a lambda-expression. Apart from this every LINQ method internally iterates your collection e.g. by using foreach or for, however it simply hides it from the client. I don’t consider this any more readable nor maintainable (think of edit your code while debugging a method containing lambda-expressions).

Having said this shouldn't use LINQ to modify items in your collection. A better way is the solution you already provided in your question. With a classic loop you can easily iterate your collection and update its items. In fact all those solutions relying on List.ForEach are nothing different, but far harder to read from my perspective.

So you shouldn't use LINQ in those cases where you want to update the elements of your collection.


Off-topic: I agree, and there are sooooooo many instances of LINQ being abused, examples of people requesting "high performance LINQ chains", to do what could be accomplished with a single loop, etc. I am thankful that NOT using LINQ is too ingrained into me, and typically do not use it. I see people using LINQ chains to perform a single action, not realizing that pretty much every time a LINQ command is used you are creating another for loop "under the hood". I feel it is syntatic sugar to create less verbose ways of doing simple tasks, not to be a replacement for standard coding.
so exactly where is your answer in all of that?
@JohnLord the answer simply is: don't use LINQ in those cases where you want to modify the collection.
J
JaredPar

There is no built-in extension method to do this. Although defining one is fairly straight forward. At the bottom of the post is a method I defined called Iterate. It can be used like so

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

Iterate Source

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

Is Iterate necessary, whats wrong with Count, Sum, Avg or other existing extension method that returns a scalar value?
this is pretty close to what i want but a little.. involved. The blog post I posted has a similar implementation but with fewer lines of code.
The IterateHelper seems overkill. The overload that doesn't take an index ends up doing alot more extra work (convert callback to lambda which takes index, keep a count which is never used). I understand it's reuse, but it's a workaround for just using a forloop anyway so it should be efficient.
@Cameron, IterateHelper serves 2 purposes. 1) Single implementation and 2) allows for ArgumentNullException to be thrown at call time vs. use. C# iterators are delayed executed, having the helper prevents the odd behavior of an exception being thrown during iteration.
@JaredPar: Except you're not using an iterator. There's no yield statement.
g
granadaCoder

I've tried a few variations on this, and I keep going back to this guy's solution.

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

Again, this is somebody else's solution. But I've compiled the code into a small library, and use it fairly regularly.

I'm going to paste his code here, for the off chance that his site(blog) ceases to exist at some point in the future. (There's nothing worse than seeing a post that says "Here is the exact answer you need", Click, and Dead 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; } );

You can use an Action<TSource> instead of creating the an extra delegate. That might not have been available as of the time of writing that, though.
Yeah, that was old school DotNet at that point in time. Good comment Frank.
The URL is dead ! (This Domain Name Has Expired) Good thing I copied the code here ! #patOnShoulder
Checking for value type makes sense, but perhaps it would be better to use a constraint, i.e. where T: struct, to catch this at compile time.
C
Community

No, LINQ doesn't support a manner of mass updating. The only shorter way would be to use a ForEach extension method - Why there is no ForEach extension method on IEnumerable?


A
AnthonyWJones

My 2 pennies:-

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

I like the thinking, but it's not really clear what the code is doing
T
Tamas Czinege

You can use LINQ to convert your collection to an array and then invoke Array.ForEach():

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

Obviously this will not work with collections of structs or inbuilt types like integers or strings.


P
PartTimeIndie

I wrote some extension methods to help me out with that.

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

I am using it like this:

        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

For reference the argument check:

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

Here is the extension method I use...

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

Why "value type elements are not supported by update"?? Nothing interferes that!
That was specific to the project I was working on. I suppose it wouldn't matter in most cases. Lately I've reworked that and renamed it Run(...), removed the value type thing and changed it to return void and dropped the count code.
That´s more or less what List<T>.ForEach also does, but just for all IEnumerable.
Looking back on this now I'd say just use a foreach loop. The only benefit of using something like this is if you want to chain the methods together and return the enumerable from the function to continue the chain after executing the action. Otherwise this is just an extra method call for no benefit.
P
Peter Mortensen

You can use Magiq, a batch operation framework for LINQ.


L
Leandro Bardelli

Some people consider this is a comment, but for me is an answer, because the right way to do something wrong is not do it. So, the answer for this question is in the question itself.

DO NOT USE LINQ to modify data. Use a loop.


I agree, but this answer already said that.
S
Stormenet

I assume you want to change values inside a query so you could write a function for it

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

But not shure if this is what you mean.


This is going in sort of the right direction by would require something to enumerate v, else it'll do nothing.
江明哲

Quoting Adi Lester's answer (https://stackoverflow.com/a/5755487/8917485)

I quite like this answer, but this answer has a bug. It just changes values in a new created list. It must be changed to two lines to read the real changed list.

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

This should have been a comment. Please wait until you can comment properly. But also, it's not true. The statement collection.ToList().ForEach(c => c.PropertyToSet = value) permanently changes all items in collection, it doesn't matter in which way they happen to get enumerated.
This does not provide an answer to the question. Once you have sufficient reputation you will be able to comment on any post; instead, provide answers that don't require clarification from the asker. - From Review
V
Vishwa G

Suppose we have data like below,

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

and if we want to modify the list and replace the existing values of the list to modified values, then first create a new empty list, then loop through data list by invoking modifying method on each list item,

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;

Why would you make something which can run in O(n) runtime execute in O(n^2) or worse? IM unaware of how specifics of linq work but i can see this is at minimum a n^2 solution for a n problem.