Обновить все объекты в коллекции с помощью LINQ


500

Есть ли способ сделать следующее с помощью LINQ?

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

Чтобы уточнить, я хочу перебрать каждый объект в коллекции, а затем обновить свойство каждого объекта.

Мой вариант использования - у меня есть куча комментариев к сообщению в блоге, и я хочу перебрать каждый комментарий к сообщению в блоге и установить дату и время для сообщения в блоге равным +10 часов. Я мог бы сделать это в SQL, но я хочу сохранить это на бизнес-уровне.


14
Интересный вопрос Лично я предпочитаю, как у вас это выше - гораздо яснее, что происходит!
— noelicus

8
Я пришел сюда в поисках ответа на тот же вопрос и решил, что будущим разработчикам будет так же просто, меньше кода и легче понять, как сделать это так, как вы это делали в своем OP.
— Кейси Крукстон

4
Почему вы хотите сделать это в LINQ?
— Caltor

13
Этот вопрос требует неправильной вещи, единственный правильный ответ: не используйте LINQ для изменения источника данных
— Тим

Я голосую, чтобы закрыть этот вопрос как не по теме, потому что почти все ответы на этот вопрос активно вредят пониманию LINQ новыми программистами.
— Танвеер Бадар

Ответы:


842

Хотя вы можете использовать ForEachметод расширения, если вы хотите использовать только фреймворк, вы можете сделать

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

Это ToListнеобходимо для того, чтобы оценить выбор сразу из-за ленивой оценки .


6
Я проголосовал за это, потому что это довольно хорошее решение ... единственная причина, по которой мне нравится метод расширения, заключается в том, что он немного яснее понять, что именно происходит ... однако ваше решение все еще довольно мило
— lomaxx

9
Если коллекция имела право ObservableCollectionголоса, то может быть полезно изменить элементы на месте, а не создавать новый список.
— Кэмерон МакФарланд

7
@desaivv Да, это немного синтаксическое злоупотребление, поэтому Решарпер предупреждает вас об этом.
— Кэмерон Макфарланд

46
ИМХО, это гораздо менее выразительно, чем простой цикл foreach. ToList () сбивает с толку, потому что он не используется ни для чего, кроме принудительного вычисления, которое иначе было бы отложено. Проекция также сбивает с толку, потому что она не используется по прямому назначению; скорее, он используется для перебора элементов коллекции и предоставления доступа к свойству, чтобы его можно было обновить. Единственный вопрос, на мой взгляд, заключается в том, может ли цикл foreach извлечь выгоду из параллелизма с использованием Parallel.ForEach, но это другой вопрос.
— Филипп

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
Это имеет преимущество перед ответом Кэмерона Макфарланда об обновлении списка на месте, а не создании нового списка.
— Саймон Тьюси

7
Вау, этот ответ действительно бесполезен. Создание новой коллекции просто для того, чтобы можно было использовать цикл
— Тим

@SimonTewsi Так как это набор объектов, список должен быть обновлен в любом случае. Коллекция будет новой, но объекты в коллекции будут такими же.
— Крис

70

Я делаю это

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

Я думаю, что это самый чистый способ сделать это.
— WCM

31
Этот подход, безусловно, работает, но он нарушает намерение All()метода расширения, что может привести к путанице, когда кто-то еще читает код.
— Том Бакстер

Этот подход лучше. Использование всех вместо каждого цикла
— 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 (по любой причине).
— Тим

@ Ранго, которого вы все еще НЕ избегаете, так 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 (вы все равно можете указать первую ссылку, если хотите). Sidenote: Rust имеет схожие функции, и в конце концов сдался и добавил эквивалентную функцию: stackoverflow.com/a/50224248/799204
— sourcejedi

15

Использование:

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 кажется излишним. Перегрузка, которая не принимает индекс, в конечном итоге делает больше дополнительной работы (преобразование обратного вызова в лямбду, которая принимает индекс, сохраняет счетчик, который никогда не используется). Я понимаю, что это повторное использование, но это обходной путь для простого использования forloop в любом случае, поэтому он должен быть эффективным.
— Кэмерон Макфарланд

2
@Cameron, IterateHelper служит 2 целям. 1) Одиночная реализация и 2) позволяет создавать исключение ArgumentNullException во время вызова по сравнению с использованием. Итераторы C # выполняются с задержкой, поскольку помощник предотвращает появление нечетного поведения исключения во время итерации.
— JaredPar

2
@JaredPar: За исключением того, что вы не используете итератор. Там нет заявления о выходе.
— Кэмерон Макфарланд

7

Хотя вы специально спрашивали о решении LINQ, и этот вопрос довольно старый, я публикую не-LINQ-решение. Это потому, что LINQ (= интегрированный в язык запрос ) предназначен для использования в запросах к коллекциям. Все LINQ-методы не изменяют базовую коллекцию, они просто возвращают новую (или, точнее, итератор в новую коллекцию). Таким образом, что бы вы ни делали, например, с помощью Select, не влияло на основную коллекцию, вы просто получаете новую.

Конечно, вы можете сделать это с ForEach(кстати, не LINQ, а расширением List<T>). Но это в любом случае буквально использует foreach, но с лямбда-выражением. Помимо этого, каждый метод LINQ внутренне выполняет итерацию вашей коллекции, например, с помощью foreachили for, однако он просто скрывает ее от клиента. Я не считаю это более читабельным и не обслуживаемым (подумайте о редактировании кода при отладке метода, содержащего лямбда-выражения).

Сказав это не следует использовать LINQ для изменения элементов в вашей коллекции. Лучший способ - это решение, которое вы уже предоставили в своем вопросе. С помощью классического цикла вы можете легко перебирать свою коллекцию и обновлять ее элементы. На самом деле все эти решения, на List.ForEachкоторые можно положиться , ничем не отличаются, но с моей точки зрения их гораздо сложнее читать.

Поэтому вам не следует использовать LINQ в тех случаях, когда вы хотите обновить элементы вашей коллекции.


3
Не по теме: я согласен, и оооочень много случаев злоупотребления LINQ, примеры людей, запрашивающих "высокопроизводительные цепочки LINQ", чтобы сделать то, что можно сделать с помощью одного цикла, и т. Д. Я благодарен, что НЕ используется LINQ слишком укоренился в меня, и, как правило, не использовать его. Я вижу людей, использующих цепочки LINQ для выполнения одного действия, не понимая, что почти каждый раз, когда используется команда LINQ, вы создаете еще один forцикл «под капотом». Я чувствую, что это синтетический сахар - создавать менее многословные способы выполнения простых задач, а не заменять стандартное кодирование.
— ForeverZer0

6

Я попробовал несколько вариантов этого, и я продолжаю возвращаться к решению этого парня.

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

1
Вы можете использовать Action<TSource>вместо создания дополнительного делегата. Это, возможно, не было доступно на момент написания этого, хотя.
— Фрэнк Дж

Да, это была старая школа DotNet на тот момент. Хороший комментарий Фрэнк.
— granadaCoder

URL мертв! (Срок действия этого доменного имени истек) Хорошо, что я скопировал код здесь! #patOnShoulder
— granadaCoder

1
Проверка на тип значения имеет смысл, но, возможно, было бы лучше использовать ограничение, т. where T: structЕ. Перехватывать его во время компиляции.
— Гру


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

Почему "элементы типа значения не поддерживаются обновлением" ?? Ничто не мешает этому!
— абатищев

Это было специфично для проекта, над которым я работал. Я полагаю, это не имеет значения в большинстве случаев. В последнее время я переработал это и переименовал в Run (...), удалил тип значения и изменил его так, чтобы он возвращал void, и удалил код счета.
— Билл Форни

Это более или менее то, что 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

0

Вы можете использовать Magiq , инфраструктуру пакетных операций для LINQ.


-3

Предположим, у нас есть данные, как показано ниже,

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;

2
Зачем вам делать что-то, что может работать в O (n) во время выполнения, в O (n ^ 2) или хуже? IM не знает, как работает специфика linq, но я вижу, что это как минимум решение ^ 2 для проблемы n .
— Fallenreaper
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.