2010-01-27 12 views
14

Sono un po 'curioso di sapere quale sia considerata "migliore pratica" quando si tratta di FirstOrDefault.FirstOrDefault() off di un LINQ rispetto a FirstOrDefault() con un Lambda?

Ho già visto questa domanda, che è simile alla domanda che ho, ma non abbastanza vicino da rispondere alla mia domanda.

Quale di questi è "codice migliore"? e perché?

var foos = GetMyEnumerableFoos(); 

var foo1 = (from f in foos 
    where f.Bar == "spider monkey" 
    select f).FirstOrDefault(); 

/* OR */ 

var foo2 = foos.FirstOrDefault(f => f.Bar == "spider monkey"); 

tendo verso quest'ultima, come IMO, rende il codice più pulito. Ma sono curioso di sapere se il "coraggio" tecnico di ciò che sta accadendo è più efficiente nell'altro senso. Questo cambia se stai usando diversi tipi di oggetti IEnumerables? come DataTables o array di stringhe o oggetti LINQ?

========= modificare ==========

Supponendo post di Jon Skeet è corretta, sono andato a cercare in Reflector per vedere cosa Dove e FirstOrDefault simile, e ecco quello che mi si avvicinò con:

Nel caso di foos.Where (f => f.Bar == "scimmia ragno") FirstOrDefault()

public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) 
{ 
    if (source == null) 
    { 
     throw Error.ArgumentNull("source"); 
    } 
    if (predicate == null) 
    { 
     throw Error.ArgumentNull("predicate"); 
    } 
    if (source is Iterator<TSource>) 
    { 
     return ((Iterator<TSource>) source).Where(predicate); 
    } 
    if (source is TSource[]) 
    { 
     return new WhereArrayIterator<TSource>((TSource[]) source, predicate); 
    } 
    if (source is List<TSource>) 
    { 
     return new WhereListIterator<TSource>((List<TSource>) source, predicate); 
    } 
    return new WhereEnumerableIterator<TSource>(source, predicate); 
} 

che confluiranno in.:

public static TSource FirstOrDefault<TSource>(this IEnumerable<TSource> source) 
{ 
    if (source == null) 
    { 
     throw Error.ArgumentNull("source"); 
    } 
    IList<TSource> list = source as IList<TSource>; 
    if (list != null) 
    { 
     if (list.Count > 0) 
     { 
      return list[0]; 
     } 
    } 
    else 
    { 
     using (IEnumerator<TSource> enumerator = source.GetEnumerator()) 
     { 
      if (enumerator.MoveNext()) 
      { 
       return enumerator.Current; 
      } 
     } 
    } 
    return default(TSource); 
} 

Nel caso di foos.FirstOrDefault (f => f.Bar == "spider monkey");

public static TSource FirstOrDefault<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) 
{ 
    if (source == null) 
    { 
     throw Error.ArgumentNull("source"); 
    } 
    if (predicate == null) 
    { 
     throw Error.ArgumentNull("predicate"); 
    } 
    foreach (TSource local in source) 
    { 
     if (predicate(local)) 
     { 
      return local; 
     } 
    } 
    return default(TSource); 
} 

Guardando che ancora mi lascia un po 'confuso, ci sarebbe efficienze aggiunti dai utilizzando l'iteratore adeguata per alcuni tipi di oggetto? O è più efficiente saltare tutto ciò e iniziare semplicemente il ciclo e il test? Il mio istinto mi dice di nuovo che è quest'ultimo.

risposta

17

Ebbene, il "selezionare" parte sarà rimosso dal compilatore in ogni caso, così sei veramente a confronto:

foo.Where(f => f.Bar == "spider monkey") 
    .FirstOrDefault() 

vs

foo.FirstOrDefault(f => f.Bar == "spider monkey") 

dubito che farà alcuna significativa differenza rispetto all'efficienza all'interno di LINQ to Objects. Personalmente utilizzerei personalmente quest'ultima versione ... a meno che non volessi riutilizzare la parte filtrante della query altrove.

+0

Questo è interessante. Ho fatto un po 'più di scavo dopo che sei stato pubblicato solo per vedere cosa sta succedendo in quei tre metodi che hai citato. Ho pubblicato i miei risultati sopra ... Certamente aiuta a rispondere alla mia domanda. –

+0

Non ho controllato, ma presumo che nessuno dei 'Dove'' restituisce 's in un' IList', quindi i due loop finali sono effettivamente gli stessi: il 'MoveNext' chiamerà il' predicato' dal 'Dove ', dove la chiamata' FirstOrDefault (predicate) 'la chiama esplicitamente. Quindi la versione 'Where' ha una chiamata extra per' false' (e il predicato '' true', se presente) finale'. –

4

Preferisco quest'ultimo semplicemente perché è più conciso.

Termini di efficienza Dubito che si possa trovare una differenza sostanziale tra i 2. Sono praticamente identici nel comportamento anche se in pratica funzionano leggermente diversi.

La più grande differenza è che il primo crea una nuova istanza IEnumerable<T> che viene quindi percorsa per il primo elemento. In quest'ultimo caso, l'istanza esistente IEnumerable<T> viene percorsa per il primo valore corrispondente al predicato. Di nuovo, molto improbabile da notare.

-1

La seconda versione sarebbe molto più efficiente.

La prima versione

var foo1 = (from f in foos 
    where f.Bar == "spider monkey" 
    select f).FirstOrDefault(); 

sarà sempre un ciclo tra tutti gli elementi, la creazione di una nuova collezione di elementi corrispondenti.

La seconda versione

var foo2 = foos.FirstOrDefault(f => f.Bar == "spider monkey"); 

sarà solo un ciclo tra gli elementi fino a quando viene trovata una corrispondenza e poi restituito.

+3

In realtà, se si mette punto di rottura nel vostro LINQ vedrete questo ... ma sarà solo eseguire questa affermazione LINQ volta. si può anche avere dichiarazioni LINQ che l'accesso IEnumerables creati da LINQ annuncio dichiarazioni fino alla nausea e sarà ancora andare solo attraverso di loro ogni volta che se si chiama FirstOrDefault. Nessun problema, però, è abituato a pensare la stessa cosa. ;) –

+1

L'istruzione "Circolerà sempre tutti gli elementi, creando una nuova raccolta di elementi corrispondenti" è falsa su due punti. In primo luogo, itererà solo attraverso tutti gli elementi in "foos" se non c'è una "scimmia ragno", altrimenti si fermerà quando colpisce la prima "scimmia ragno". In secondo luogo, non creerà una collezione di oggetti corrispondenti, ma restituirà la prima. –

+2

Penso che @phdesign sia stato confuso a causa della presenza delle parentesi - ha assunto che tutto ciò che è all'interno viene eseguito per primo (perché è quello che fanno le parentesi di solito). Tuttavia, questa regola non si applica in questo caso, sono presenti solo come costrutto di raggruppamento. –

Problemi correlati