2009-12-30 10 views
9

Digamos que tengo una colección de mensajes que tiene las propiedades "UserID" (int) y "Unread" (bool).¿Cómo puedo iterar sobre una colección y cambiar los valores con los métodos de extensión LINQ?

¿Cómo puedo usar los métodos de extensión LINQ para establecer Unread = false, para cualquier mensaje en la colección en cuyo UserID = 5?

Por lo tanto, sé que puedo hacer algo como:

messages.Any(m => m.UserID == 5); 

Pero, ¿cómo se ajusta la propiedad no leído de cada uno de los que tienen un método de extensión, así?

Nota: Sé que no debería hacer esto en el código de producción. Simplemente estoy tratando de aprender un poco más de LINQ-fu.

+1

¿Hay alguna razón por la que no pueda usar un iterativo regular para cada iteración sobre la colección filtrada? – helios

+0

@helios, no. Esto no es código de producción. Solo me estoy divirtiendo y tenía curiosidad si podía repetir los elementos usando LINQ – KingNestor

+2

Solo por convención y por la cordura general, llámalo leído, no no leído. if (m.Read) es mucho más fácil de entender que if (! m.Unread). –

Respuesta

6

En realidad, esto es posible utilizando sólo los métodos de extensión incorporada en LINQ sin ToList.
Creo que esto funcionará de manera muy similar a un ciclo for normal. (No he comprobado)

¿No dare hacer esto en código real.

messages.Where(m => m.UserID == 5) 
     .Aggregate(0, (m, r) => { m.Unread = false; return r + 1; }); 

Como una ventaja adicional, esto devolverá el número de usuarios que modificó.

+1

Oh wow. Eso es genial. – KingNestor

+1

¿Podría explicar por qué no deberíamos hacer esto en el código de producción? – ChrisO

+1

@ChrisO: es demasiado ilegible. – SLaks

5

messages.Where(m => m.UserID == 5).ToList().ForEach(m => m.Unread = false);

luego enviar los cambios.

+0

'Any' devuelve un bool, por lo que no es correcto, es probable que desee' Where' –

+0

Cualquiera no es correcto porque comprueba si hay al menos un mensaje con UserID 5 en la recopilación y devuelve verdadero o falso. Debes usar Where en lugar de any. También tenga en cuenta que la creación de una lista puede ser costosa cuando la colección es realmente grande. Ver también mi respuesta que sugiere el método de extensión de MoreLinq ForEach. –

+3

Seleccionar no es correcto - va a devolver un 'IEnumerable '. Quieres decir 'Donde'. –

2

Con LINQ no puede porque LINQ es un lenguaje de consulta/extensión. Sin embargo, hay un proyecto llamado MoreLinq, que define un método de extensión llamado ForEach que le permite pasar una acción que se realizará en cada elemento.

Por lo tanto, usted podría hacer con MoreLinq:

messages.Where(m => m.UserID == 5).ForEach(m => m.Unread = false); 

Best Regards,
Oliver Hanappi

+1

Esto no es estrictamente cierto. La razón por la que LINQ no puede hacer esto no tiene nada que ver con el hecho de que es un lenguaje de consulta/extensión. (Además del hecho de que LINQ puede hacer esto) – SLaks

4

métodos estándar de extensión de LINQ no incluye efectos secundarios métodos dirigidos. Sin embargo se puede aplicar ya sea por sí mismo o utilizar de Reactive Extensions for .NET (Rx) así:

messages.Where(m => m.UserID == 5).Run(m => m.Unread = false); 
3

Como no hay un método de extensión explícito que haga un ForEach, tiene que utilizar una biblioteca secundaria o escribir la declaración foreach por su cuenta.

foreach (Message msg in messages.Where(m => m.UserID == 5)) 
{ 
    msg.Unread = false; 
} 

Si realmente desea utilizar una declaración de LINQ para lograr esto, cree una copia de la colección utilizando el método ToList(), el acceso al procedimiento del tipo ListForEach():

messages.Where(m => m.UserID == 5).ToList().ForEach(m => m.Unread = false); 

o coloque la cara -Efecto en un comunicado Where():

messages.Where(m => 
{ 
    if (m.UserID == 5) { m.Unread = false; return true; } 
    return false; 
}); 

en cualquier caso, prefiero utilizar el exp lícito foreach loop ya que no hace copias innecesarias y es más claro que el truco Where.

+0

Llamar a 'Seleccionar' no hará nada hasta que se enumere el resultado. Debería llamar a '.Count()' en el valor de retorno de 'Seleccionar' para forzar la enumeración de toda la colección. Además, su primer 'Select' debería ser' Where'. – SLaks

+0

Atrapado inmediatamente después de la publicación. –

+0

El último método todavía no funcionará. Llamar a 'Where' no hará nada hasta que se enumere su valor de retorno. Debe llamar a '.Count()'. Además, te perdiste un paréntesis de cierre. – SLaks

0

Esta respuesta es en el espíritu de proporcionar una solución. Activado podría crear una extensión que haga tanto el predicado (extensión Where) para descartar los elementos y la acción necesaria sobre esos elementos.

A continuación se muestra una extensión llamada OperateOn que es bastante fácil de escribir:

public static void OperateOn<TSource>(this List<TSource> items, 
             Func<TSource, bool> predicate, 
             Action<TSource> operation) 
{ 
    if ((items != null) && (items.Any())) 
    { 
     items.All (itm => 
     { 
      if (predicate(itm)) 
       operation(itm); 

      return true; 
     }); 

    } 
} 

Aquí está en acción:

var myList = new List<Item> 
        { new Item() { UserId = 5, Name = "Alpha" }, 
        new Item() { UserId = 5, Name = "Beta", UnRead = true }, 
        new Item() { UserId = 6, Name = "Gamma", UnRead = false } 
        }; 


myList.OperateOn(itm => itm.UserId == 5, itm => itm.UnRead = true); 

Console.WriteLine (string.Join(" ", 
           myList.Select (itm => string.Format("({0} : {1})", 
                    itm.Name, 
                    itm.UnRead)))); 

/* Outputs this to the screen 

(Alpha : True) (Beta : True) (Gamma : False) 

*/ 

...

public class Item 
{ 
    public bool UnRead { get; set; } 
    public int UserId { get; set; } 
    public string Name { get; set; } 
} 
0

Debe estar capaz de hacerlo solo en un Seleccionar(), recuerda que el lambda es un atajo para una función, por lo que puedes poner tanta lógica allí como lo desee, luego devuelva el elemento actual que se enumera. Y ... ¿por qué exactamente no harías esto en el código de producción?

messages = messages 
    .Select(m => 
    { 
     if (m.UserId == 5) 
      m.Unread = true; 
     return m; 
    }); 
Cuestiones relacionadas