Оновіть усі об'єкти в колекції за допомогою LINQ


499

Чи є спосіб зробити наступне за допомогою LINQ?

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

Для уточнення я хочу повторити кожен об'єкт у колекції, а потім оновити властивість кожного об'єкта.

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


14
Цікаве запитання. Особисто я віддаю перевагу тому, як ви це зрозуміли вище - набагато зрозуміліше, що відбувається!
noelicus

8
Я прийшов сюди, шукаючи відповідь на те саме питання, і вирішив, що майбутнім розробникам це так само просто, менше коду і простіше зрозуміти, щоб це зробити так, як ви робили в вас ОП.
Кейсі Крукстон

4
Чому ви хочете це робити в LINQ?
Caltor

13
Це запитання вимагає неправильної речі. Єдина правильна відповідь: не використовуйте LINQ для зміни джерела даних
Тім Шмелтер

Я голосую, щоб закрити це питання поза темою, оскільки майже всі відповіді на це питання активно шкідливі для розуміння LINQ нових програмістів.
Tanveer Badar

Відповіді:


841

Хоча ви можете використовувати ForEachметод розширення, якщо ви хочете використовувати лише рамки, які ви можете зробити

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

ToListНеобхідно для того , щоб оцінити відразу вибрати з - за ледачу оцінку .


6
Я підтримав це, тому що це досить приємне рішення ... Єдиною причиною, що мені подобається метод розширення, є те, що він робить трохи зрозумілішим, щоб зрозуміти, що саме відбувається ... проте ваше рішення все ще досить солодке
lomaxx

9
Якщо колекція була ObservableCollectionприказкою, то корисність зміни елементів замість створення нового списку може бути корисною.
Камерон Макфарланд

7
@desaivv Так, це трохи зловживання синтаксисом, тому Resharper попереджає вас про це.
Камерон Макфарланд

46
ІМХО, це набагато менш виразно, ніж простий цикл передбачень. 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; })
Дан вигадує Firelight

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 (з будь-якої причини).
Тім Шмелтер

@Rango, якого ви все ще НЕ уникаєте, 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: Іржа має схожі риси, і в кінці кінців здався і додав еквівалентну функцію: stackoverflow.com/a/50224248/799204
sourcejedi

15

Використання:

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

Я не впевнений, це надмірне використання LINQ чи ні, але це спрацювало для мене, коли я хотів оновити певні елементи у списку для конкретних умов.


7

Для цього не існує вбудованого методу розширення. Хоча визначення одного досить прямо. Внизу допису розміщений метод, який я визначив під назвою Ітерація. Це можна використовувати так

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

Чи необхідний ітерат, що не так з 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 (...), видалив річ типу значень і змінив її на повернення недійсним і скинув код рахунку.
Білл Форні

Це більш-менш те, що 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) або гірше? Я не знаю, як працює специфіка linq, але я можу побачити, що це як мінімум ^ 2 рішення для російської проблеми.
Fallenreaper
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.