Da questo post inizieremo ad analizzare tutti i metodi che compongono LinqToSQL e le loro rispettive implementazioni, liberamente consultabili sul sito http://referencesource.microsoft.com. La maggior parte dei metodi LinqToSQL sono extension method definiti nella classe statica Enumerable e applicabili ad oggetti che implementano l’interfaccia IEnumerable<T> (quindi la versione generica). Il primo metodo che affrontiamo è la Where che viene utilizzata per filtrare gli elementi di una successione. A grandi linee il funzionamento di questo metodo è:

Data una successione qualunque di elementi e una condizione, gli elementi della successione vengono controllati ad uno ad uno, se soddisfano la condizione vengono immediatamente restituiti.

La successione è individuata dall'oggetto sul quale chiamiamo la Where mentre la condizione è individuata da una funzione che deve essere passata come parametro. In particolare il metodo è composto da due overload:

public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
 
public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, int, bool> predicate)

I due overload differiscono per la firma della funzione che accettano in ingress. Analizziamo per iniziare il secondo overload. Esso accetta una funzione che prende in input una istanza della classe generica T, fissata dalla successione di elementi su cui stiamo chiamando il metodo, e un intero che il quale sarà automaticamente valorizzato con l'indice della posizione di ogni elemento considerato. Facciamo un esempio per capire meglio:

Considerato un vettore con i primi 5 numeri pari [2,4,6,8,10] la funzione-condizione in questo caso ha firma Func<int, int, bool> e i valori che gli saranno automaticamente passati sono le coppie:

-          (2,0)

-          (4,1)

-          (6,2)

-          (8,3)

-          (10,4)

L'implementazione del metodo è la seguente

public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, int, bool> predicate) {
            if (source == null) throw Error.ArgumentNull("source");
            if (predicate == null) throw Error.ArgumentNull("predicate");
            return WhereIterator<TSource>(source, predicate);
        }

Come possiamo vedere il metodo fa solamente una validazione dei parametri in ingresso e poi delega ad un altro metodo la logica di implementazione.

static IEnumerable<TSource> WhereIterator<TSource>(IEnumerable<TSource> source, Func<TSource, int, bool> predicate) {
            int index = -1;
            foreach (TSource element in source) {
                checked { index++; }
                if (predicate(element, index)) yield return element;
            }
        } 

Il codice effettivo conta solamente 4 righe in cui:

- viene definita la variabile index.php che conterrà l'indice di ogni elemento.

- viene effettuata una foreach sulla successione da filtrare

- dentro al ciclo viene incrementato di uno l'indice all'interno di un blocco checked. Questo vuol dire che questo overload funziona con successioni lunghe al massimo int.MaxValue. Questo perché il blocco checked controlla che l'operazione su index sia tra i limiti imposti dagli int. Solitamente non è così e sommando 1 a int.MaxValue avremmo ottenuto int.MinValue.

- una if controlla se la coppia elemento-indice soddisfa la condizione ed in caso restituisce immediatamente l'elemento con uno yield return.

L'implementazione dell'altro overload è più lunga e la vedremo nel post successivo.

comments powered by Disqus