Actualice todos los objetos en una colección usando LINQ

500

¿Hay alguna manera de hacer lo siguiente usando LINQ?

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

Para aclarar, quiero iterar a través de cada objeto en una colección y luego actualizar una propiedad en cada objeto.

Mi caso de uso es que tengo un montón de comentarios en una publicación de blog, y quiero repetir cada comentario en una publicación de blog y establecer la fecha y hora en la publicación del blog en +10 horas. Podría hacerlo en SQL, pero quiero mantenerlo en la capa empresarial.

lomaxx
fuente
14
Interesante pregunta. Personalmente prefiero cómo lo tienes arriba, ¡mucho más claro lo que está sucediendo!
noelicus
8
Vine aquí buscando una respuesta a la misma pregunta, y decidí que era igual de fácil, menos código y más fácil de entender para que los futuros desarrolladores lo hicieran como lo hicieron en su OP.
Casey Crookston
44
¿Por qué querrías hacerlo en LINQ?
Caltor
13
Esta pregunta pide algo incorrecto, la única respuesta correcta es: no use LINQ para modificar la
fuente de
Estoy votando para cerrar esta pregunta como fuera de tema porque casi todas las respuestas a esta pregunta son activamente perjudiciales para la comprensión de los nuevos programadores de LINQ.
Tanveer Badar

Respuestas:

842

Si bien puede usar un ForEachmétodo de extensión, si desea usar solo el marco, puede hacerlo

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

Se ToListnecesita para evaluar la selección inmediatamente debido a una evaluación diferida .

Cameron MacFarland
fuente
66
Voté esto porque es una solución bastante buena ... la única razón por la que me gusta el método de extensión es que hace que sea un poco más claro entender exactamente lo que está sucediendo ... sin embargo, su solución sigue siendo bastante dulce
lomaxx
99
Si la colección fue una ObservableCollectionvoz, entonces puede ser útil cambiar los elementos en lugar de crear una nueva lista.
Cameron MacFarland
77
@desaivv sí, esto es un poco de abuso de sintaxis, por lo que Resharper te está advirtiendo sobre esto.
Cameron MacFarland
46
En mi humilde opinión, esto es mucho menos expresivo que un simple bucle foreach. ToList () es confuso porque no se usa para otra cosa que no sea forzar la evaluación que de otro modo se aplazaría. La proyección también es confusa porque no se usa para el propósito previsto; más bien, se usa para iterar sobre los elementos de la colección y permitir el acceso a una propiedad para que pueda actualizarse. La única pregunta en mi mente sería si el bucle foreach podría o no beneficiarse del paralelismo usando Parallel.ForEach, pero esa es una pregunta diferente.
Philippe
37
Esta respuesta es una peor práctica. Nunca hagas esto.
Eric Lippert
351
collection.ToList().ForEach(c => c.PropertyToSet = value);
Ε Г И І И О
fuente
36
@SanthoshKumar: Usocollection.ToList().ForEach(c => { c.Property1ToSet = value1; c.Property2ToSet = value2; });
Ε Г И І И О
@CameronMacFarland: Por supuesto que no, ya que las estructuras son inmutables. Pero si realmente quieres, puedes hacer esto:collection.ToList().ForEach(c => { collection[collection.IndexOf(c)] = new <struct type>() { <propertyToSet> = value, <propertyToRetain> = c.Property2Retain }; });
Ε Г И І И О
11
Esto tiene la ventaja sobre la respuesta de Cameron MacFarland de actualizar la lista en su lugar, en lugar de crear una nueva lista.
Simon Tewsi
77
Wow, esta respuesta realmente no es útil. Crear una nueva colección solo para poder usar un bucle
Tim Schmelter
@SimonTewsi Dado que es una colección de objetos, la lista debe actualizarse de todos modos. La colección será nueva, pero los objetos de la colección serán los mismos.
Chris
70

yo estoy haciendo esto

Collection.All(c => { c.needsChange = value; return true; });
Rahul
fuente
Creo que esta es la forma más limpia de hacerlo.
wcm
31
Este enfoque ciertamente funciona, pero viola la intención del All()método de extensión, lo que lleva a una posible confusión cuando alguien más lee el código.
Tom Baxter
Este enfoque es mejor. Usar todo en lugar de usar cada ciclo
UJS
2
Definitivamente prefiero esto a llamar a ToList () innecesariamente, incluso si es un poco engañoso sobre para qué está usando All ().
iupchris10
1
Si está utilizando una colección como List<>esa, el ForEach()método es una forma mucho menos críptica de lograr esto. exForEach(c => { c.needsChange = value; })
Dan Is Fiddling By Firelight
27

En realidad encontré un método de extensión que hará bien lo que quiero

public static IEnumerable<T> ForEach<T>(
    this IEnumerable<T> source,
    Action<T> act)
{
    foreach (T element in source) act(element);
    return source;
}
lomaxx
fuente
44
nice :) Lomaxx, tal vez agregue un ejemplo para que la gente pueda verlo en 'acción' (¡boom tish!).
Pure.Krome
2
Este es el único enfoque útil si realmente desea evitar un foreachbucle (por cualquier razón).
Tim Schmelter
@Rango que aún NO estás evitando foreachya que el código en sí contiene el foreachbucle
GoldBishop
@GoldBishop seguro, el método oculta el bucle.
Tim Schmelter
1
El enlace está roto, ahora está disponible en: codewrecks.com/blog/index.php/2008/08/13/… . También hay un comentario de blog que enlaza con stackoverflow.com/questions/200574 . A su vez, el comentario de la pregunta principal enlaza con blogs.msdn.microsoft.com/ericlippert/2009/05/18/… . Quizás la respuesta sería más simple reescrita usando el MSDN (aún podría acreditar el primer enlace si lo desea). Nota al margen: Rust tiene características similares, y finalmente cedió y agregó la función equivalente: stackoverflow.com/a/50224248/799204
sourcejedi
15

Utilizar:

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

No estoy seguro de si esto está utilizando LINQ en exceso o no, pero me ha funcionado cuando quiero actualizar un elemento específico de la lista para una condición específica.

Albanés
fuente
7

No hay un método de extensión incorporado para hacer esto. Aunque definir uno es bastante sencillo. Al final de la publicación hay un método que definí llamado Iterate. Se puede usar así

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

Fuente iterada

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++;
    }
}
JaredPar
fuente
¿Es necesario iterar, qué tiene de malo contar, sumar, promediar u otro método de extensión existente que devuelva un valor escalar?
AnthonyWJones
2
esto está bastante cerca de lo que quiero pero un poco ... involucrado. La publicación de blog que publiqué tiene una implementación similar pero con menos líneas de código.
lomaxx
1
El IterateHelper parece exagerado. La sobrecarga que no toma un índice termina haciendo mucho más trabajo adicional (convertir la devolución de llamada a lambda que toma el índice, mantener un recuento que nunca se usa). Entiendo que es reutilizar, pero de todos modos es una solución para usar un forloop, por lo que debería ser eficiente.
Cameron MacFarland
2
@Cameron, IterateHelper tiene 2 propósitos. 1) Implementación única y 2) permite lanzar ArgumentNullException en el momento de la llamada frente al uso. Los iteradores de C # se retrasan en la ejecución, y el ayudante evita que se produzca un comportamiento extraño de una excepción durante la iteración.
JaredPar
2
@JaredPar: Excepto que no estás usando un iterador. No hay declaración de rendimiento.
Cameron MacFarland
7

Aunque solicitó específicamente una solución LINQ y esta pregunta es bastante antigua, publico una solución que no es LINQ. Esto se debe a que LINQ (= consulta integrada en el idioma ) está destinado a ser utilizado para consultas en colecciones. Todos los métodos LINQ no modifican la colección subyacente, solo devuelven una nueva (o un iterador más preciso a una nueva colección). Por lo tanto, hagas lo que hagas, por ejemplo, con unSelect no afecta la colección subyacente, simplemente obtienes una nueva.

Por supuesto, podría hacerlo con un ForEach(que no es LINQ, por cierto, sino una extensión activada List<T>). Pero esto literalmente se usa de foreachtodos modos, pero con una expresión lambda. Aparte de esto, cada método LINQ itera internamente su colección, por ejemplo, utilizando foreacho for, sin embargo, simplemente la oculta del cliente. No considero que esto sea más legible ni mantenible (piense en editar su código mientras depura un método que contiene expresiones lambda).

Dicho esto, no debería usar LINQ para modificar elementos en su colección. Una mejor manera es la solución que ya proporcionó en su pregunta. Con un bucle clásico, puede iterar fácilmente su colección y actualizar sus elementos. De hecho, todas esas soluciones en las que se basan List.ForEachno son nada diferentes, pero son mucho más difíciles de leer desde mi perspectiva.

Por lo tanto, no debe usar LINQ en aquellos casos en los que desea actualizar los elementos de su colección.

HimBromBeere
fuente
3
Fuera de tema: estoy de acuerdo, y hay muchísimos casos de abuso de LINQ, ejemplos de personas que solicitan "cadenas de LINQ de alto rendimiento", para hacer lo que se podría lograr con un solo bucle, etc. Estoy agradecido de que NO usar LINQ sea demasiado arraigado en mí, y típicamente no lo uso. Veo personas que usan cadenas LINQ para realizar una sola acción, sin darse cuenta de que casi cada vez que se usa un comando LINQ está creando otro forbucle "bajo el capó". Siento que es azúcar sintético crear formas menos detalladas de realizar tareas simples, no ser un reemplazo para la codificación estándar.
ForeverZer0
6

He intentado algunas variaciones sobre esto, y sigo volviendo a la solución de este tipo.

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

Nuevamente, esta es la solución de otra persona. Pero he compilado el código en una pequeña biblioteca y lo uso con bastante regularidad.

Voy a pegar su código aquí, por la posibilidad de que su sitio (blog) deje de existir en algún momento en el futuro. (No hay nada peor que ver una publicación que dice "Aquí está la respuesta exacta que necesita", Click y Dead URL).

    public static class UpdateExtensions {

    public delegate void Func<TArg0>(TArg0 element);

    /// <summary>
    /// Executes an Update statement block on all elements in an IEnumerable<T> sequence.
    /// </summary>
    /// <typeparam name="TSource">The source element type.</typeparam>
    /// <param name="source">The source sequence.</param>
    /// <param name="update">The update statement to execute for each element.</param>
    /// <returns>The numer of records affected.</returns>
    public static int Update<TSource>(this IEnumerable<TSource> source, Func<TSource> update)
    {
        if (source == null) throw new ArgumentNullException("source");
        if (update == null) throw new ArgumentNullException("update");
        if (typeof(TSource).IsValueType)
            throw new NotSupportedException("value type elements are not supported by update.");

        int count = 0;
        foreach (TSource element in source)
        {
            update(element);
            count++;
        }
        return count;
    }
}



int count = drawingObjects
        .Where(d => d.IsSelected && d.Color == Colors.Blue)
        .Update(e => { e.Color = Color.Red; e.Selected = false; } );
granadaCoder
fuente
1
Puede usar un en Action<TSource>lugar de crear un delegado adicional. Sin embargo, eso podría no haber estado disponible al momento de escribir eso.
Frank J
Sí, eso era DotNet de la vieja escuela en ese momento. Buen comentario Frank.
granadaCoder
¡La URL está muerta! (Este nombre de dominio ha caducado) ¡Qué bueno que copié el código aquí! #patOnShoulder
granadaCoder
1
Verificar el tipo de valor tiene sentido, pero quizás sería mejor usar una restricción, es decir where T: struct, detectar esto en tiempo de compilación.
Groo
3

Escribí algunos métodos de extensión para ayudarme con eso.

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

Lo estoy usando así:

        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

Como referencia, el argumento verifica:

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

    }
}
PartTimeIndie
fuente
2

Mis 2 centavos: -

 collection.Count(v => (v.PropertyToUpdate = newValue) == null);
AnthonyWJones
fuente
77
Me gusta pensar, pero no está realmente claro lo que está haciendo el código
lomaxx
2

Puede usar LINQ para convertir su colección en una matriz y luego invocar Array.ForEach ():

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

Obviamente, esto no funcionará con colecciones de estructuras o tipos incorporados como enteros o cadenas.

Tamas Czinege
fuente
1

Aquí está el método de extensión que uso ...

    /// <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;
    }
Bill Forney
fuente
¿Por qué "los elementos de tipo de valor no son compatibles con la actualización"? ¡Nada interfiere eso!
abatishchev
Eso fue específico para el proyecto en el que estaba trabajando. Supongo que no importaría en la mayoría de los casos. Últimamente, lo modifiqué y le cambié el nombre a Ejecutar (...), eliminé el tipo de valor y lo cambié para devolver nulo y solté el código de conteo.
Bill Forney el
Eso es más o menos lo que List<T>.ForEachtambién hace, pero solo para todos IEnumerable.
HimBromBeere
Mirando hacia atrás en esto ahora, diría que solo use un bucle foreach. El único beneficio de usar algo como esto es si desea encadenar los métodos juntos y devolver lo enumerable de la función para continuar la cadena después de ejecutar la acción. De lo contrario, este es solo un método adicional que no requiere ningún beneficio.
Bill Forney
0

Supongo que desea cambiar los valores dentro de una consulta para poder escribir una función para ella

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

Pero no estoy seguro si esto es lo que quieres decir.

Stormenet
fuente
Esto va en la dirección correcta, ya que requeriría algo para enumerar v, de lo contrario, no hará nada.
AnthonyWJones
0

Puede usar Magiq , un marco de trabajo por lotes para LINQ.

ivos
fuente
-3

Supongamos que tenemos datos como a continuación,

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

y si queremos modificar la lista y reemplazar los valores existentes de la lista por valores modificados, primero creemos una nueva lista vacía, luego recorramos la lista de datos invocando el método de modificación en cada elemento de la lista,

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;
Vishwa G
fuente
2
¿Por qué harías que algo que puede ejecutarse en tiempo de ejecución O (n) se ejecute en O (n ^ 2) o peor? No estoy al tanto de cómo funcionan los detalles de linq, pero puedo ver que esto es, como mínimo, una solución ^ 2 para un problema n .
Fallenreaper