lunes, 18 de agosto de 2014

Ejecución Diferida (Lazy Loading)




La ejecución perezosa o diferida (Lazy Loading) es el comportamiento por defecto que usa LinQ para ejecutar, valga la redundancia, sus métodos extensores. A grandes rasgos viene a significar que los métodos no se ejecutan en el momento de realizar su llamada, sino que se ejecutan en el momento en que consumimos (utilizamos) alguno de los datos devueltos por esta llamada. Con todo esto el motor de LinQ se asegura el no realizar cálculos, cargas de datos, etc., que el usuario no necesite en ese momento.





Recuerda que aquí tienes el indice de todos los posts del Curso de LinQ.



Vamos a ver como se produce toda esta magia.

Para ello empezaremos explicando la manera de realizar nuestros métodos ‘enumerables’ mediante la palabra reservada yield return.

 Para que nuestro método puede ser enumerable, debe devolver un IEnumerable o un IEnumerable<T> ya que debe convertirse en un bloque iterador.

Para entender desde cero su funcionamiento vamos a hacer un ejemplo completo, y para ello vamos construirnos un método extensor enumerable. El método será muy sencillito y extenderá la clase string agregando la funcionalidad de devolver cada una de las letras de una palabra, en valores string y no char como realiza por defecto, lo llamaremos ToEnumerableString.

Cuando nos metemos en la implementación de uno de estos métodos, debemos olvidarnos del enfoque clásico de me creo una colección de resultado, la relleno y la devuelvo, ya que en nuestros métodos enumerables lo que haremos será ir devolviendo los valores que forman la colección, elemento a elemento.

public static class Extensiones
{
    public static IEnumerable<string> ToEnumerableString(this string source)
    {
        var enumerator = source.GetEnumerator();
 
        while (enumerator.MoveNext())
        {
            // En cuanto tengo un elemento lo devuelvo
            yield return enumerator.Current.ToString();
        }
    }
}

Como podemos comprobar nuestro método devuelve un IEnumerable<string>, y cada vez que está a disposición de tener listo un elemento, lo devuelve con la cláusula Yield Return (en un método que devolvería una colección, este sería el momento en que añadiríamos elemento a la colección de resultado).

Una vez tenemos el método extensor vamos a ver un ejemplo de uso con carga perezosa, para ver su funcionamiento es muy importante copiar el código y jugar con el F11 o poner un punto de ruptura en el método extensor para ver el momento en que la ejecución entra en el método.

static void Main(string[] args)
{
    string nombre = "Paco";
    /// Aquí debería de llamar al método, pero no lo hace
    var letras = nombre.ToEnumerableString();

    foreach (var letra in letras)
    {
        /// Hace una llamada al método según va necesitando elementos
        Console.WriteLine(letra);
    }
}


Como podemos ver, en el momento en que llama al método nombre.ToEnumerableString(), no realiza la ejecución del mismo, no es hasta su uso dentro del foreach que el compilador traslada la ejecución a nuestro método extensor, en nuestro ejemplo 4 llamadas, una por cada una de las letras de “Paco”. Por lo que no se utiliza hasta que es necesario.


Vamos a ver ahora un ejemplo modificado en el que solo imprimiremos la primera palabra, y podremos comprobar que solo hace una única llamada al método extensor, por lo que ahorrará recursos y tiempo en no tener que realizar la carga de los otros 3 caracteres.

static void Main(string[] args)
{
    string nombre = "Paco";
    /// Aquí debería de llamar al método, pero no lo hace
    var letras = nombre.ToEnumerableString();
 
    var primeraLetra = letras.First();
 
    /// Solo hace una llamada al método
    Console.WriteLine(primeraLetra);       
}


Todos los métodos extensores de LinQ utilizan esta técnica, que en ocasiones cuando realicemos consultas cruzadas, veremos que no es tan buena como puede parecer, ya que puede realizar repeticiones de llamadas innecesarias. Todo esto lo paliaremos con los métodos extensores ToList() y ToArray(), que obligaran a realizar la ejecución del método completo y que veremos más adelante.