Mettre à jour tous les objets d'une collection à l'aide de LINQ


500

Existe-t-il un moyen d'effectuer les opérations suivantes à l'aide de LINQ?

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

Pour clarifier, je veux parcourir chaque objet d'une collection, puis mettre à jour une propriété sur chaque objet.

Mon cas d'utilisation est que j'ai un tas de commentaires sur un article de blog, et je veux parcourir chaque commentaire sur un article de blog et définir la date / heure sur l'article de blog à +10 heures. Je pourrais le faire en SQL, mais je veux le garder dans la couche métier.


14
Question interessante. Personnellement, je préfère comment vous l'avez obtenu ci-dessus - beaucoup plus clair ce qui se passe!
noelicus

8
Je suis venu ici à la recherche d'une réponse à la même question et j'ai décidé qu'il était tout aussi facile, moins de code et plus facile à comprendre pour les futurs développeurs de le faire comme vous l'avez fait dans votre OP.
Casey Crookston

4
Pourquoi voudriez-vous le faire dans LINQ?
Caltor

13
Cette question demande la mauvaise chose, la seule bonne réponse est: n'utilisez pas LINQ pour modifier la source de données
Tim Schmelter

Je vote pour fermer cette question comme hors sujet car presque toutes les réponses à cette question nuisent activement à la compréhension des nouveaux programmeurs de LINQ.
Tanveer Badar

Réponses:


842

Bien que vous puissiez utiliser une ForEachméthode d'extension, si vous souhaitez utiliser uniquement le framework, vous pouvez le faire

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

Le ToListest nécessaire pour évaluer la sélection immédiatement en raison d'une évaluation paresseuse .


6
J'ai voté pour cela parce que c'est une très bonne solution ... la seule raison pour laquelle j'aime la méthode d'extension, c'est qu'elle rend un peu plus clair la compréhension exacte de ce qui se passe ... mais votre solution est encore assez douce
lomaxx

9
Si la collecte était un ObservableCollectionmot à dire, il peut être utile de modifier les éléments en place plutôt que de créer une nouvelle liste.
Cameron MacFarland

7
@desaivv oui, c'est un peu un abus de syntaxe, alors Resharper vous en avertit.
Cameron MacFarland

46
À mon humble avis, c'est beaucoup moins expressif qu'une simple boucle foreach. La ToList () prête à confusion car elle n'est utilisée que pour forcer une évaluation qui serait autrement différée. La projection est également déroutante car elle n'est pas utilisée conformément à sa destination; il est plutôt utilisé pour parcourir les éléments de la collection et permettre l'accès à une propriété afin qu'elle puisse être mise à jour. La seule question dans mon esprit serait de savoir si la boucle foreach pourrait bénéficier du parallélisme en utilisant Parallel.ForEach, mais c'est une question différente.
Philippe

37
Cette réponse est une pire pratique. Ne fais jamais ça.
Eric Lippert

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

36
@SanthoshKumar: Utilisationcollection.ToList().ForEach(c => { c.Property1ToSet = value1; c.Property2ToSet = value2; });
Ε Г И І И О

@CameronMacFarland: Bien sûr que non car les structures sont immuables. Mais si vous voulez vraiment, vous pouvez le faire:collection.ToList().ForEach(c => { collection[collection.IndexOf(c)] = new <struct type>() { <propertyToSet> = value, <propertyToRetain> = c.Property2Retain }; });
Ε Г И І И О

11
Cela a l'avantage sur la réponse de Cameron MacFarland de mettre à jour la liste en place, plutôt que de créer une nouvelle liste.
Simon Tewsi

7
Wow, cette réponse n'est vraiment pas utile. Créer une nouvelle collection juste pour pouvoir utiliser une boucle
Tim Schmelter

@SimonTewsi Puisqu'il s'agit d'une collection d'objets, la liste doit de toute façon être mise à jour sur place. La collection sera nouvelle, mais les objets de la collection seront les mêmes.
Chris

70

Je fais ça

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

Je pense que c'est la façon la plus propre de le faire.
wcm

31
Cette approche fonctionne certainement, mais elle viole l'intention de la All()méthode d'extension, conduisant à une confusion potentielle lorsque quelqu'un d'autre lit le code.
Tom Baxter

Cette approche est meilleure .Utiliser tout au lieu d'utiliser chaque boucle
UJS

2
Je préfère vraiment cela plutôt que d'appeler ToList () inutilement, même si c'est un peu trompeur sur la raison pour laquelle il utilise All ().
iupchris10

1
Si vous utilisez une collection comme List<>celle-ci, la ForEach()méthode est un moyen beaucoup moins cryptique pour y parvenir. exForEach(c => { c.needsChange = value; })
Dan Is Fiddling By Firelight

27

J'ai trouvé une méthode d'extension qui fera ce que je veux bien

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

4
nice :) Lomaxx, peut-être ajouter un exemple pour que les voyants puissent le voir dans 'action' (boom tish!).
Pure.Krome

2
C'est la seule approche utile si vous voulez vraiment éviter une foreachboucle (pour une raison quelconque).
Tim Schmelter

@Rango que vous n'évitez toujours pas foreachcar le code lui-même contient la foreachboucle
GoldBishop

@GoldBishop bien sûr, la méthode masque la boucle.
Tim Schmelter

1
Le lien est rompu, il est désormais disponible sur: codewrecks.com/blog/index.php/2008/08/13/… . Il y a aussi un commentaire de blog qui renvoie à stackoverflow.com/questions/200574 . À son tour, le commentaire de la question principale renvoie à blogs.msdn.microsoft.com/ericlippert/2009/05/18/… . Peut-être que la réponse serait plus simple réécrite en utilisant le MSDN (vous pouvez toujours créditer le premier lien si vous le souhaitez). Sidenote: Rust a des fonctionnalités similaires, et a finalement cédé et ajouté la fonction équivalente: stackoverflow.com/a/50224248/799204
sourcejedi

15

Utilisation:

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

Je ne sais pas si cela utilise trop LINQ ou non, mais cela a fonctionné pour moi lorsque je voulais mettre à jour un élément spécifique de la liste pour une condition spécifique.


7

Il n'y a pas de méthode d'extension intégrée pour ce faire. Bien que la définition de l'un soit assez simple. Au bas du message, il y a une méthode que j'ai définie appelée Iterate. Il peut être utilisé comme tel

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

Itérer la 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++;
    }
}

Iterate est-il nécessaire, qu'est-ce qui ne va pas avec Count, Sum, Avg ou toute autre méthode d'extension existante qui renvoie une valeur scalaire?
AnthonyWJones

2
c'est assez proche de ce que je veux mais un peu .. impliqué. Le billet de blog que j'ai publié a une implémentation similaire mais avec moins de lignes de code.
lomaxx

1
Le IterateHelper semble exagéré. La surcharge qui ne prend pas d'index finit par faire beaucoup plus de travail supplémentaire (convertir le rappel en lambda qui prend l'index, garder un compte qui n'est jamais utilisé). Je comprends que c'est une réutilisation, mais c'est une solution de contournement pour utiliser simplement une boucle de toute façon, donc elle devrait être efficace.
Cameron MacFarland,

2
@Cameron, IterateHelper sert 2 objectifs. 1) L'implémentation unique et 2) permet de lancer ArgumentNullException au moment de l'appel par rapport à l'utilisation. Les itérateurs C # sont retardés dans l'exécution, l'aide de l'assistant empêche le comportement étrange d'une exception levée pendant l'itération.
JaredPar

2
@JaredPar: Sauf que vous n'utilisez pas d'itérateur. Il n'y a pas de déclaration de rendement.
Cameron MacFarland

7

Bien que vous ayez spécifiquement demandé une solution LINQ et que cette question soit assez ancienne, je poste une solution non LINQ. En effet, LINQ (= requête intégrée de langue ) est destiné à être utilisé pour les requêtes sur les collections. Toutes les méthodes LINQ ne modifient pas la collection sous-jacente, elles en renvoient juste une nouvelle (ou plus précisément un itérateur dans une nouvelle collection). Ainsi, quoi que vous fassiez par exemple avec un Selectn'affecte pas la collection sous-jacente, vous en obtenez simplement une nouvelle.

Bien sûr toi pouvez le faire avec un ForEach(qui n'est pas LINQ, soit dit en passant, mais une extension activée List<T>). Mais cela utilise littéralement deforeach toute façon, mais avec une expression lambda. En dehors de cela, chaque méthode LINQ itère en interne votre collection, par exemple en utilisant foreachou for, mais elle la cache simplement au client. Je ne considère pas cela plus lisible ni maintenable (pensez à éditer votre code tout en déboguant une méthode contenant des expressions lambda).

Cela dit, cela ne devrait pas utiliser LINQ pour modifier les éléments de votre collection. Une meilleure façon est la solution que vous avez déjà fournie dans votre question. Avec une boucle classique, vous pouvez facilement itérer votre collection et mettre à jour ses articles. En fait, toutes ces solutions qui List.ForEachcomptent ne sont pas différentes, mais beaucoup plus difficiles à lire de mon point de vue.

Vous ne devez donc pas utiliser LINQ dans les cas où vous souhaitez mettre jour les éléments de votre collection.


3
Hors sujet: je suis d'accord, et il y a tellement de cas d'abus de LINQ, des exemples de personnes demandant des "chaînes LINQ hautes performances", pour faire ce qui pourrait être accompli avec une seule boucle, etc. Je suis reconnaissant que NE PAS utiliser LINQ soit trop ancrée en moi et ne l'utilise généralement pas. Je vois des gens utiliser des chaînes LINQ pour effectuer une seule action, ne réalisant pas qu'à peu près chaque fois qu'une commande LINQ est utilisée, vous créez une autre forboucle "sous le capot". Je pense que c'est du sucre syntaxique de créer des façons moins verbeuses de faire des tâches simples, et non de remplacer le codage standard.
ForeverZer0

6

J'ai essayé quelques variantes à ce sujet, et je reviens toujours à la solution de ce type.

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

Encore une fois, c'est la solution de quelqu'un d'autre. Mais j'ai compilé le code dans une petite bibliothèque et je l'utilise assez régulièrement.

Je vais coller son code ici, pour la chance que son site (blog) cesse d'exister à un moment donné dans le futur. (Il n'y a rien de pire que de voir un article qui dit "Voici la réponse exacte dont vous avez besoin", Click et URL morte.)

    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
Vous pouvez utiliser un Action<TSource>au lieu de créer un délégué supplémentaire. Cela n'était peut-être pas disponible au moment de la rédaction de cela, cependant.
Frank J

Oui, c'était la vieille école DotNet à ce moment-là. Bon commentaire Frank.
granadaCoder

L'URL est morte! (Ce nom de domaine a expiré) Heureusement, j'ai copié le code ici! #patOnShoulder
granadaCoder

1
Vérifier le type de valeur est logique, mais il serait peut-être préférable d'utiliser une contrainte, c'est where T: struct-à- dire de l'attraper au moment de la compilation.
Groo


3

J'ai écrit quelques méthodes d'extension pour m'aider.

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

Je l'utilise comme ceci:

        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

Pour référence, la vérification des arguments:

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

Mes 2 centimes: -

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

7
J'aime la réflexion, mais ce que fait le code n'est pas vraiment clair
lomaxx

2

Vous pouvez utiliser LINQ pour convertir votre collection en un tableau, puis appeler Array.ForEach ():

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

Évidemment, cela ne fonctionnera pas avec des collections de structures ou de types intégrés comme des entiers ou des chaînes.


1

Voici la méthode d'extension que j'utilise ...

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

Pourquoi "les éléments de type valeur ne sont pas pris en charge par la mise à jour" ?? Rien n'interfère avec ça!
abatishchev

C'était spécifique au projet sur lequel je travaillais. Je suppose que cela n'aurait pas d'importance dans la plupart des cas. Dernièrement, j'ai retravaillé cela et l'ai renommé Run (...), supprimé le type de valeur et l'ai changé pour retourner void et supprimer le code de comptage.
Bill Forney

C'est plus ou moins ce qui fait List<T>.ForEachaussi, mais juste pour tous IEnumerable.
HimBromBeere

En y repensant maintenant, je dirais qu'il suffit d'utiliser une boucle foreach. Le seul avantage d'utiliser quelque chose comme ceci est si vous voulez chaîner les méthodes ensemble et renvoyer l'énumérable de la fonction pour continuer la chaîne après avoir exécuté l'action. Sinon, ce n'est qu'un appel de méthode supplémentaire sans aucun avantage.
Bill Forney

0

Je suppose que vous souhaitez modifier les valeurs dans une requête afin que vous puissiez écrire une fonction pour elle

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

Mais ne vous inquiétez pas si c'est ce que vous voulez dire.


Cela va en quelque sorte dans la bonne direction, il faudrait quelque chose pour énumérer v, sinon cela ne fera rien.
AnthonyWJones

0

Vous pouvez utiliser Magiq , un framework d'opérations par lots pour LINQ.


-3

Supposons que nous ayons des données comme ci-dessous,

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

et si nous voulons modifier la liste et remplacer les valeurs existantes de la liste par des valeurs modifiées, créer d'abord une nouvelle liste vide, puis parcourir la liste de données en appelant la méthode de modification sur chaque élément de la liste,

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
Pourquoi voudriez-vous faire exécuter quelque chose qui peut s'exécuter dans O (n) runtime dans O (n ^ 2) ou pire? IM ne sait pas comment les spécificités de linq fonctionnent mais je peux voir que c'est au moins une solution ^ 2 pour un problème n .
Fallenreaper
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.