2009-12-30 10 views
9

Diciamo che ho una collezione di messaggi che ha le proprietà "UserID" (int) e "Unread" (bool).In che modo è possibile scorrere su una raccolta e modificare i valori con i metodi di estensione LINQ?

Come utilizzare i metodi di estensione LINQ per impostare Unread = false, per qualsiasi messaggio nella raccolta in cui ID utente = 5?

Quindi, so che posso fare qualcosa di simile:

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

Ma, come faccio a impostare la proprietà non letto di ciascuno di quelli con un metodo di estensione così?

Nota: so che non dovrei farlo nel codice di produzione. Sto semplicemente cercando di imparare un po 'di LINQ-fu.

+1

C'è qualche motivo per cui non è possibile utilizzare un normale per-ogni iterazione sulla raccolta filtrata? – helios

+0

@elios, no. Questo non è un codice di produzione. Mi sto solo divertendo ed ero curioso di poter iterare sugli articoli usando LINQ – KingNestor

+2

Solo per convenzione e sanità pubblica, chiamalo letto, non letto. se (m.Read) è molto più facile da capire rispetto a (! m.Unread). –

risposta

6

In realtà, questo è possibile utilizzando solo il built-in metodi di estensione LINQ senza ToList.
Credo che ciò si comporterà in modo molto simile a un normale ciclo for. (Non ho controllato)

Non si osare fare questo nel codice reale.

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

Come bonus aggiuntivo, questo restituirà il numero di utenti che ha modificato.

+1

Oh wow. E 'molto bello. – KingNestor

+1

Puoi spiegare perché non dovremmo farlo nel codice di produzione? – ChrisO

+1

@ Chris: È troppo illeggibile. – SLaks

5

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

quindi inviare le modifiche.

+0

'Any' restituisce un valore bool, quindi non è corretto. Probabilmente vorrai' Where' –

+0

Qualsiasi non è corretto perché controlla se vi è almeno un messaggio con UserID 5 nella raccolta e restituisce true o false. Devi usare Where invece di qualsiasi. Inoltre, sii consapevole del fatto che creare una lista può essere costoso quando la collezione è veramente grande. Vedi anche la mia risposta che suggerisce il metodo di estensione di MoreLinq ForEach. –

+3

Select is not right - restituirà un oggetto "IEnumerable ". Intendi "Dove". –

2

Con LINQ non è possibile perché LINQ è una lingua/estensione di query. Esiste comunque un progetto chiamato MoreLinq, che definisce un metodo di estensione chiamato ForEach che consente di passare un'azione che verrà eseguita su ogni elemento.

Quindi, si potrebbe fare con MoreLinq:

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

migliori saluti,
Oliver Hanappi

+1

Questo non è assolutamente vero. Il motivo per cui LINQ non può farlo non ha nulla a che fare con il fatto che si tratta di un linguaggio/estensione di query. (Oltre al fatto che LINQ può farlo) – SLaks

4

standard metodi di estensione LINQ non include gli effetti collaterali metodi volti. Tuttavia è possibile implementare da soli o utilizzare da Reactive Extensions for .NET (Rx) come questo:

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

Poiché non esiste un metodo di estensione esplicito che faccia un ForEach, si è bloccati con l'utilizzo di una libreria secondaria o scrivendo l'istruzione foreach da soli.

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

Se davvero si vuole utilizzare una dichiarazione LINQ per ottenere questo risultato, creare una copia della collezione utilizzando il metodo ToList(), l'accesso al metodo del tipo ListForEach():

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

o posizionare il lato -Effetto in una dichiarazione Where():

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

in entrambi i casi, io preferisco usare l'exp liceo foreach loop in quanto non esegue copie non necessarie ed è più chiaro rispetto all'hack Where.

+0

Chiamare "Seleziona" non farà nulla finché il risultato non viene enumerato. Dovresti chiamare '.Count()' sul valore di ritorno di Select' per forzare l'intera collezione ad essere enumerata. Inoltre, il tuo primo "Seleziona" dovrebbe essere "Dove". – SLaks

+0

Catturato subito dopo la pubblicazione. –

+0

L'ultimo metodo non funzionerà ancora. Chiamare 'Where' non farà nulla finché il suo valore di ritorno non viene enumerato. Devi chiamare '.Count()'. Inoltre, hai perso una parentesi di chiusura. – SLaks

0

Questa risposta è nello spirito di fornire una soluzione. Su potrebbe creare un'estensione che esegue sia il predicato (estensione Where) per eliminare gli elementi e l'azione necessaria su tali elementi.

Di seguito è un'estensione di nome OperateOn che è abbastanza facile da scrivere:

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

    } 
} 

Ecco in azione:

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

Si dovrebbe essere in grado di farlo in un Select(), ricorda che lambda è una scorciatoia per una funzione, quindi puoi mettere tanta logica lì come vuoi, quindi restituire l'oggetto corrente che viene enumerato. E ... perché esattamente non lo faresti nel codice di produzione?

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