11

Si consideri il seguente codice C#:Comprensioni elenco C# = puro zucchero sintattico?

IEnumerable numbers = Enumerable.Range(0, 10); 
var evens = from num in numbers where num % 2 == 0 select num; 

È questo zucchero sintattico puro per permettere a me di scrivere un ciclo for o foreach come un one-liner? Ci sono ottimizzazioni del compilatore sotto le copertine che rendono la comprensione delle liste sopra più efficiente del costrutto del ciclo? Come funziona questo sotto il cofano?

risposta

14

Come ha detto Jason, il codice è equivalente a:

Enumerable.Range(0, 10).Where(n => n % 2 == 0); 

Nota lambda sarà trasformata in una chiamata di funzione che è fatto per ogni elemento. Questa è probabilmente la parte più grande del sovraccarico. Ho fatto un test, che indica LINQ è circa 3 volte più lento (mono GMCs versione 1.2.6.0) su questo compito esatto

 
    Time for 10000000 for loop reps: 00:00:17.6852560 
    Time for 10000000 LINQ reps: 00:00:59.0574430 

    Time for 1000000 for loop reps: 00:00:01.7671640 
    Time for 1000000 LINQ reps: 00:00:05.8868350 

EDIT: Gishu riferisce che VS2008 e il quadro v3.5 SP1 dà:

 
    Time for 1000000 loop reps: :00.3724585 
    Time for 1000000 LINQ reps: :00.5119530 

LINQ è circa 1,4 volte più lento.

Confronta un ciclo for e un elenco LINQ (e qualsiasi struttura utilizzi internamente). In entrambi i casi, converte il risultato in un array (necessario per forzare LINQ a smettere di essere "pigro"). Entrambe le versioni ripetono:

using System; 
using System.Collections.Generic; 
using System.Diagnostics; 
using System.Linq; 

public class Evens 
{ 
    private static readonly int[] numbers = new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; 

    private static int MAX_REPS = 1000000; 

    public static void Main() 
    { 
     Stopwatch watch = new Stopwatch(); 

     watch.Start(); 
     for(int reps = 0; reps < MAX_REPS; reps++) 
     { 
      List<int> list = new List<int>(); // This could be optimized with a default size, but we'll skip that. 
      for(int i = 0; i < numbers.Length; i++) 
      { 
       int number = numbers[i]; 
       if(number % 2 == 0) 
        list.Add(number); 
      } 
      int[] evensArray = list.ToArray(); 
     } 
     watch.Stop(); 
     Console.WriteLine("Time for {0} for loop reps: {1}", MAX_REPS, watch.Elapsed); 

     watch.Reset(); 
     watch.Start(); 
     for(int reps = 0; reps < MAX_REPS; reps++) 
     { 
      var evens = from num in numbers where num % 2 == 0 select num; 
      int[] evensArray = evens.ToArray(); 
     } 
     watch.Stop(); 
     Console.WriteLine("Time for {0} LINQ reps: {1}", MAX_REPS, watch.Elapsed); 
    } 
} 

test di performance passate su operazioni simili (per esempio LINQ vs Loop - A performance test) corroborare questo.

+0

Lo stai facendo su Mono? Sei sicuro che questo sia paragonabile a Microsoft IL? –

+2

Mono utilizza MSIL, che è anche noto come CIL dopo la standardizzazione. –

+2

Sì, ma ciò non significa che i due compilatori stiano creando un output equivalente. –

5

È possibile semplificare il codice ulteriormente

var evens = Enumerable.Range(0, 10).Where(n => n % 2 == 0); 

Un vantaggio di questa forma è che l'esecuzione di tale espressione è rimandata evens viene iterata (foreach(var n in evens) { ... }). L'affermazione sopra dice semplicemente al compilatore di catturare l'idea di come enumerare i numeri pari tra 0 e 10, ma non eseguire su quell'idea fino a quando non è assolutamente necessario.

1

Nel codice precedente, si dispone di una query Linq, che si ripete su IEnumerable in modo funzionale allo stesso modo di un ciclo foreach. Tuttavia, nel tuo codice, c'è un sacco in corso sotto il cofano. Il foreach è probabilmente molto più efficiente se si intende scrivere un ciclo ad alte prestazioni. Linq è destinato a uno scopo diverso (accesso ai dati generalizzato).

L'interfaccia IEnumerable espone un metodo iteratore, che viene quindi chiamato continuamente da un costrutto di ciclo, come una query foreach o Linq. L'iteratore restituisce l'elemento successivo nella raccolta ogni volta che viene chiamato.

+0

Con il ciclo 'for' intendevo sia' for' che 'foreach' ... Ho modificato la domanda per riflettere che ... –

+0

Jonas, ho aggiornato la mia risposta per riflettere i vostri commenti. –

+0

OK, ma diciamo che invece di fare qualcosa di semplice come nell'esempio sopra ho uno scenario che è più simile a questo: ho una lista e voglio filtrare tutto il Person.LastName che corrisponde ad una certa espressione regolare. Io uso la sintassi di comprensione dell'elenco per iterare sopra l'elenco e passo l'espressione regolare come espressione lambda. Sarebbe più efficiente della scrittura di un ciclo? –

4

LINQ funziona in modo diverso per diversi tipi di dati. Gli stai dando da mangiare oggetti, quindi usa LINQ-to-objects. Questo viene tradotto in codice simile a un semplice ciclo for.

Ma LINQ supporta diversi tipi di dati.Ad esempio, se si avesse una tabella di db chiamata 'numeri', LINQ-to-SQL tradurrebbe la stessa query;

var evens = from num in numbers where num % 2 == 0 select num; 

in SQL come questo;

select num from numbers where num % 2 = 0 

e quindi lo esegue. Si noti che non esegue il filtraggio creando un ciclo for con un blocco "if" all'interno; la clausola 'where' modifica la query inviata al server di database. La traduzione dalla query al codice eseguito è specifica per il tipo di dati che stai alimentando.

Quindi no, LINQ non è solo zucchero sintattico per i cicli for, ma un sistema molto più coinvolto per la "compilazione" delle query. Per maggiori informazioni, cercare Linq Provider. Questi sono i componenti come linq-to-objects e linq-to-xml, e puoi scrivere il tuo se ti senti coraggioso.

Problemi correlati