Inicialización con System.Lazy<T> en .NET Framework 4.0

Una de las novedades de .NET Framework 4.0 es la presentación de la clase System.Lazy<T>. En programación, una inicialización Lazy (el término Lazy significa, literalmente, gandul o retardado) es un método de inicialización que permite que el objeto no se instancie hasta que se llame por primera vez, es decir hasta que sea utilizado. En el ciclo de vida de una aplicación, los métodos y propiedades de una clase que componen existen, es decir se instancian en memoria y permanecen ahí desde la creación  hasta que la destrucción de la clase.

Una clase Customer dónde el constructor carga una lista genérica de Order –inicializa un objecto List<Order> – éste permanece en memoria pese a que incluso es posible que ni siquiera lo necesitemos. Hasta .NET Framework 4.0 la implantación de este tipo de inicializaciones se realiza mediante programación retrasando la inicialización de un objeto hasta que realmente se necesitara; en el caso de la clase Customer, por ejemplo, instanciaríamos el objeto List<Order> en un método LoadOrders() o GetOrders(), por ejemplo. En el caso de multithreading, la cosa se complica aún más y a veces el planteamiento o diseño de la clase no es suficiente.

Como novedad esta característica ya forma parte en .NET Framework 4.0 a través de System.Lazy<T> y aporta soporte Thread-Safety y una política de propagación de excepciones consistente para dar soporte al multithreading. 

A partir de ahora nos olvidaremos de las técnicas anteriores y utilizaremos System.Lazy<T> para inicializaciones perezosas. Veamos un ejemplo:

   1: static void Main(string[] args)
   2: {
   3:     Lazy<string> cadena = new Lazy<string>(delegate
   4:                                                 {
   5:                                                     return "Ejemplo Lazy";
   6:                                                 });
   7:  
   8:     Console.WriteLine(string.Format("isValueCreated: {0}", cadena.IsValueCreated));
   9:     //podemos ejecutar cualquier acción y no se ejecutará hasta...
  10:     Console.WriteLine(string.Format("Value calling: {0}", cadena.Value));
  11:     //... que no llamemos al valor
  12:     Console.WriteLine(string.Format("isValueCreated: {0}", cadena.IsValueCreated));
  13:  
  14:     Console.Read();
  15: }

El constructor de System.Lazy<T> soporta las siguientes sobrecargas:

   1: public Lazy();
   2: public Lazy(Func<T> valueFactory);
   3: public Lazy(bool isThreadSafe);
   4: public Lazy(Func<T> valueFactory, bool isThreadSafe);

El parámetro isThreadSafe se establecerá a true cuando necesitemos utilizar el objeto de forma concurrente por multiples threads, con valor predeterminado a false.

El parámetro valueFactory es un delegado del tipo Func<T> y será el encargado de inicializar el valor del objeto Lazy. Un ejemplo, además del expuesto en el ejemplo anterior, podría ser:

   1: public class Order {}
   2:  
   3: public class OrderDb
   4: {
   5:     public static List<Order> GetOrdersBy(string idCustomer)
   6:     {
   7:         return new List<Order>
   8:                    {
   9:                        new Order(),
  10:                        new Order(),
  11:                        new Order()
  12:                    };
  13:     }
  14: }
  15:  
  16: public class Customer
  17: {
  18:     public Lazy<List<Order>> _orders;
  19:  
  20:     public Customer(string id)
  21:     {
  22:         //inicialización Lazy
  23:         _orders =new Lazy<List<Order>>(() =>  OrderDb.GetOrdersBy(id) );
  24:     }
  25: }

Con el siguiente programa de consola:

   1: class Program
   2: {
   3:     static void Main(string[] args)
   4:     {
   5:         Customer customer = new Customer("10");
   6:  
   7:         //pese a que customer se ha incializado la Lista de Orders aún no:
   8:         Console.WriteLine(string.Format("isValueCreated: {0}", customer._orders.IsValueCreated));
   9:         //Solicitamos las ordenes
  10:         Console.WriteLine(string.Format("calling Value.Count: {0}", customer._orders.Value.Count));
  11:         //Ahora si se han instanciado
  12:         Console.WriteLine(string.Format("isValueCreated: {0}", customer._orders.IsValueCreated));
  13:  
  14:         Console.Read();
  15:     }
  16: }

Cuya salida será:

image

Es importante tener en mente este tipo de inicializaciones sobre todo en procesos de mucho consumo de recursos, por ejemplo objetos de la capa de datos que almacenan datos binarios – imagen, video,-  y que muchas veces no necesitamos que consuman memoria si no se van a utilizar. Como este ejemplo, muchos más…

5 comentarios en “Inicialización con System.Lazy<T> en .NET Framework 4.0”

  1. mmmmm… estamos en lo de siempre. términos de este tipo es muy común en english pero cuando se traducen,,, no sabes bien q poner…

    gracias chaval!!!

  2. Muy chulo … yo pal libro le puse algo asi como “inicialización tardía de objetos” pero es cierto … se tendría que llamar “el lazy de los cojones q se los guarda hasta q se le ocurre” 😀

    Salu2

  3. Buen post…

    Es interesante, pero ya puestos, me hubiera gustado un operador de conversión implícita entre Lazy y T:

    public static implicit operator T (Lazy @this)
    {
    return @this.Value;
    }

    Así podríamos pasar sin más un Lazy en cualquier sitio donde se esperase T (asumiendo con ello que se inicializa el Lazy)…

    Saludos!

    PD: Yo, con mi inglés de Igualada, lo de “lazy” lo hubiese traducido como Lluís… 😛

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *