Disquisendo di LINQ presso alcuni clienti mi sono reso conto che molti sviluppatori non ne hanno ben chiaro il funzionamento percui ricorre spesso la frase “Bello, ma LINQ costringe a inutili iterazioni”.

Esempio: Prendiamo un array di interi e supponiamo di volere enumerare i valori >10 interrompendo il ciclo quando il valore contenuto è uguale a 12.

foreach (int v in values)
{
  if (v > 10)
  {
    Console.WriteLine("Value is: {0}", v);
    if (v > 12)  break;
  }
}
in questo caso al terzo elemento l'enumerazione si interrompe.

Con LINQ il codice precedente può essere ricondotto a:
IEnumerable<int> query = values.Where(v => v > 10)
                               .Select(v => v);
foreach (int value in query)
{
  Console.WriteLine("Value is:{0}", value);
  if (value == 12) break;
}

A questo punto la critica è: “Ok, ma Where enumera tutti gli elementi e passa a Select quelli che soddisfano il criterio”
In realtà non è cosi e per dimostrarlo aggiungiamo al nostro esempio una classe con gli stessi extension methods esposti da Enumerable che, essendo in scope, verrano usati al posto di quelli presenti in System.Linq:

public static class Extensions
{
  public static IEnumerable<T> Where<T> (this IEnumerable<T> items, Func<T, bool> filter)
  {
    int count = 0;
    Console.WriteLine("*Entering Where*");
    foreach (T item in items)
    {
      Console.WriteLine("Where:{0}", count++);
      if (filter(item)) yield return item;
    }
  }

  public static IEnumerable<U> Select<T, U> (this IEnumerable<T> sequence, Func<T, U> selector)
  {
    int count = 0;
    Console.WriteLine("*Entering Select*");
    foreach (var item in sequence)
    {
      Console.WriteLine("Select: {0}", count++);
      yield return selector(item);
    }
  }
}

Eseguendo il codice l'output ottenuto è il seguente:

image

dal quale si denota:

  • La Select viene eseguita prima di Where.
  • La sequenza non viene completamente enumerata.
  • Ad ogni enumerazione dei risultati corrisponde uno step di enumerazione della query.

Seguendo step-by-step l'esecuzione della query il tutto è ancora più evidente.

In pratica, definendo Helper l'equivalente della classe Extensions che non fa uso di extension methods è come se scrivessimo:

IEnumerable<int> query=Helper.Select(Helper.Where(values,v=>v>10),v=>v);

Ovviamente questo non si applica nel caso si faccia uso di conversion operators tipo ToList();

Technorati Tags: ,