395

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! - noelicus
  • 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. - Casey Crookston
  • Why would you want to do it in LINQ? - Caltor
  • This question asks for the wrong thing, the only correct answer is: don't use LINQ to modify the datasource - Rango

15 답변


689

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.


  • I upvoted this because it's a pretty nice solution... the only reason I like the extension method is that it makes it a little clearer to understand exactly what is going on... however your solution is still pretty sweet - lomaxx
  • If collection was an ObservableCollection say, then changing items in place rather than creating a new list can be useful. - Cameron MacFarland
  • @desaivv yeah this is a bit of a syntax abuse, so Resharper is warning you about this. - Cameron MacFarland
  • 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. - Philippe
  • Can someone explain why is this the highest voted answer? Why is this better then using ForEach? - Igor Meszaros

263

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


  • What should i do to update more than one property? - iamCR
  • @SanthoshKumar: Use collection.ToList().ForEach(c => { c.Property1ToSet = value1; c.Property2ToSet = value2; }); - Ε Г И І И О
  • I am using VB.NET, this is not working for me :( - kbvishnu
  • This has the advantage over Cameron MacFarland's answer of updating the list in place, rather than creating a new list. - Simon Tewsi
  • Wow, this answer is really not useful. Creating a new collection just to be able to use a loop - Rango

56

I am doing this

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


  • I think this is the cleanest way to do it. - wcm
  • This approach certainly works but it violates the intent of the All() extension method, leading to potential confusion when someone else reads the code. - Tom Baxter
  • This approach is better .Using All instead of using each loop - UJS
  • Definitely prefer this over calling ToList() unnecessarily, even if it's a little misleading about what it's using All() for. - iupchris10

19

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!). - Pure.Krome
  • This is the only useful approach if you really want to avoid a foreach-loop (for whatever reason). - Rango
  • @Rango which you are still NOT avoiding foreach as the code itself contains the foreach loop - GoldBishop
  • @GoldBishop sure, the method hides the loop. - Rango

12

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.


5

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? - AnthonyWJones
  • 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. - lomaxx
  • 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 MacFarland
  • @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
  • @JaredPar: Except you're not using an iterator. There's no yield statement. - Cameron MacFarland

4

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. - Frank J

3

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?


3

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 - lomaxx
  • Yeah I'm not sure I'd want this in production code. - Cameron MacFarland

2

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

    }
}


2

Although you specifically asked for a linq-solution and this question is quite old I post a non-linq-solution. This is because linq (=lanuguage integrated query) is ment 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 shoulnd´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 solutuions 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. - ForeverZer0

1

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.


1

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! - abatishchev
  • 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. - Bill Forney
  • That aligns it with Reactive extensions Run method... - Bill Forney
  • That´s more or less what List<T>.ForEach also does, but just for all IEnumerable. - HimBromBeere
  • 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. - Bill Forney

1

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


0

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. - AnthonyWJones

Linked


Related

Latest