Обновить все объекты в коллекции с помощью 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.