2012-04-09 7 views
6

Se sto provando a filtrare i risultati a più livelli di un grafico di oggetti IEnumerable<T>, c'è un modo preferito di concatenare i metodi di estensione per farlo?Qual è il modo preferito (efficace e leggibile) di concatenare i metodi di estensione IEnumerable <T>?

Sono aperto a qualsiasi metodo di estensione e utilizzo lambda, ma preferirei non utilizzare la sintassi LINQ per rimanere coerente con il resto del codebase.

È meglio spingere il filtro sul metodo selector del metodo SelectMany() o semplicemente su un altro metodo di catena Where()? O c'è una soluzione migliore?

Come scegliere l'opzione migliore? In questo test case, tutto è direttamente disponibile in memoria. Ovviamente entrambi i campioni sottostanti stanno attualmente producendo gli stessi risultati corretti; Sto solo cercando una ragione per cui l'uno o l'altro (o un'altra opzione) sarebbe preferibile.

public class Test 
{ 
    // I want the first chapter of a book that's exactly 42 pages, written by 
    // an author whose name is Adams, from a library in London. 
    public Chapter TestingIEnumerableTExtensionMethods() 
    { 
     List<Library> libraries = GetLibraries(); 

     Chapter chapter = libraries 
      .Where(lib => lib.City == "London") 
      .SelectMany(lib => lib.Books) 
      .Where(b => b.Author == "Adams") 
      .SelectMany(b => b.Chapters) 
      .First(c => c.NumberOfPages == 42); 

     Chapter chapter2 = libraries 
      .Where(lib => lib.City == "London") 
      .SelectMany(lib => lib.Books.Where(b => b.Author == "Adams")) 
      .SelectMany(b => b.Chapters.Where(c => c.NumberOfPages == 42)) 
      .First(); 
    } 

Ed ecco il grafico oggetto del campione:

public class Library 
{ 
    public string Name { get; set; } 
    public string City { get; set; } 
    public List<Book> Books { get; set; } 
} 

public class Book 
{ 
    public string Name { get; set; } 
    public string Author { get; set; } 
    public List<Chapter> Chapters { get; set; } 
} 

public class Chapter 
{ 
    public string Name { get; set; } 
    public int NumberOfPages { get; set; } 
} 
+1

La leggibilità sembra uguale. Le prestazioni dipendono dal fatto che si tratti di linq per oggetti o di linq per qualsiasi altra cosa; perché non lo misuri e vedi? – phoog

+0

le tue query sono sempre sotto forma di 'libraries => books => chapters' - oppure potresti avere relazioni più complesse (immagino che questo sia solo un modello di test) – NSGaga

+0

@NSGaga Sono sempre in quella forma gerarchica, ma hai ragione che è solo un modello di test per renderlo più facile da capire per i lettori. Tecnicamente, sto lavorando con strutture EDI (ANSI X12), quindi file, ISA, GS e segmenti ST. –

risposta

1

Dipende da come le sottostanti opere di provider LINQ. Per LINQ to Objects, entrambi in questo caso richiederebbero la stessa quantità di lavoro, più o meno. Ma questo è l'esempio più semplice (più semplice), quindi oltre a ciò è difficile dirlo.

2

Immagino che la prima espressione che avremo sarà leggermente, ma insignificante, più veloce. Per determinare veramente se uno o l'altro è più veloce, è necessario cronometrarli, con un profiler o un cronometro.

La leggibilità non sembra essere fortemente influenzata in entrambi i casi. Preferisco il primo approccio, in quanto ha meno livelli di nidificazione. Tutto dipende dalle tue preferenze personali.

+0

Sì, il primo sarà più veloce perché non stai creando un iteratore per ogni oggetto. – usr

3

Quale è probabilmente la migliore varia in base all'implementazione LINQ che si sta utilizzando. LinqToSql si comporterà in modo diverso dal filtraggio in memoria. L'ordine delle clausole dovrebbe influire sulle prestazioni a seconda dei dati utilizzati, poiché le implementazioni ingenue filtreranno più record in precedenza nella sequenza, il che significa meno lavoro per i metodi successivi.

Per i vostri due esempi, direi che la differenza di prestazioni è trascurabile e preferirei la prima poiché consente una modifica più semplice di ciascuna clausola indipendentemente dalle altre.

Come per determinare l'opzione migliore, è la stessa di qualsiasi altra cosa: misurare.

0

Questo potrebbe dare una diversa angolazione, anche se è più una questione di stile ...
a volte mi ritrovo a fare qualcosa di simile ...

return libraries.Filter(
     l => l.City == "", 
     l => l.Books, 
     b => b.Author == "Adams", 
     b => b.Chapters, 
     c => c.NumberOfPages == 42 
     ); 

... dove si può indovinare che cosa l'extensiion è, qualcosa come ...

public static IEnumerable<TC> Filter<TL, TB, TC>(this IEnumerable<TL> list, 
    Func<TL, bool> whereLs, 
    Func<TL, IEnumerable<TB>> selectBs, 
    Func<TB, bool> whereBs, 
    Func<TB, IEnumerable<TC>> selectCs, 
    Func<TC, bool> whereCs 
    ) 
{ 
    return list 
     .Where(whereLs) 
     .SelectMany(selectBs) 
     .Where(whereBs) 
     .SelectMany(selectCs) 
     .Where(whereCs); 
} 

... o ....

...  
{ 
    return list 
     .Where(whereLs) 
     .SelectMany(l => selectBs(l).Where(whereBs)) 
     .SelectMany(b => selectCs(b).Where(whereCs)); 
} 

E le combinazioni/opzioni sono molte, a seconda di quello che hai, di come ti piace avere il tuo codice '(astrarre un po' di più o 'catturare', 'parametrizzare' ad es. PerCityAuthorPages(_city, _author, _numPages); ecc.)

...in fondo, non mi piace avere tutti i "Dove", "Seleziona" ecc. e per me non è leggibile (neanche). Mentre con la "forma breve" è abbastanza chiaro quale è, dove, selezionare ecc. Ed è molto "a mano corta" e in molto meno caratteri.

Inoltre, è possibile Deffer la decisione su dove/Select combinazioni per poi (fanno uno o l'altro in base alle esigenze, provider)

E @Telastyn è giusto, i fornitori di LINQ, per esempio se si guarda ad alcuni codici di implementazione,
con tutte le espressioni che riducono ecc.
sono piuttosto non deterministici (cioè da provider a provider) in un modo che potrebbero finire per mappare ad es. SQL
sebbene questo dovrebbe mappare lo stesso in più credo.

Problemi correlati