Este artículo proviene de un motor de traducción automática.

Tecnología de vanguardia

No se preocupe, relájese

Dino Esposito

Dino EspositoEn el desarrollo de software, la pereza término hace referencia a retrasar alguna actividad costoso tan largo como posible mucho más que se refiere a la inactividad. Pereza de software consiste en seguir haciendo las cosas, pero significa que cualquier acción llevará a cabo sólo cuando se necesita para completar una tarea determinada. En este sentido, pereza es un patrón importante en el desarrollo de software y puede que se apliquen correctamente a una gran variedad de escenarios, como el diseño e implementación.

Por ejemplo, una de las prácticas de codificación fundamentales de la metodología de Extreme Programming se resume simplemente como “ que no están Gonna Need It, ” que es una invitación explícita lento e incorporar en el código base sólo las características que necesita, y sólo cuando se necesitan.

De forma diferente, durante la implementación de una clase, quizás desee ser lenta en cuanto a la carga de datos de un origen que no se encuentra por lo tanto, más barato tener acceso. El modelo de carga diferida, de hecho, se muestra cómo la solución comúnmente aceptada de disponer de un miembro de una clase definida, pero se mantiene vacía hasta que su contenido es realmente necesario para algún otro código de cliente. Carga diferida encaja perfectamente en el contexto de las herramientas de asignación relacional de objetos (ORM) como, por ejemplo, el Entity Framework y NHibernate. Herramientas ORM se utilizan para asignar estructuras de datos entre el mundo orientado a objetos y bases de datos relacionales. En este contexto, la carga diferida hace referencia a la capacidad de un marco de trabajo de carga, para
ejemplo, pedidos para un cliente sólo cuando el código intenta leer la propiedad de colección expuesta de pedidos en una clase de cliente.

Cargar lento, no obstante, no se limita a cada implementación específica de escenarios como, por ejemplo, la programación de ORM. Por otra parte, carga diferida es no obtiene una instancia de datos antes de que sea realmente útil. En otras palabras, se carga diferida, consiste en tener lógica de fábrica especial que realiza un seguimiento de lo que debe crearse y crea automáticamente cuando el contenido se solicita al final.

En Microsoft .NET Framework, los desarrolladores de hemos tenido largo implementar cualquier comportamiento diferida manualmente en nuestras clases. Nunca se ha integrado máquinas para facilitar esta tarea. No, hasta la llegada de .NET Framework 4, es decir, donde se puede iniciar
aprovechar la nueva clase Lazy <T>.

Cumplir con los de Lazy <T> Class

El Lazy <T> es un generador especial que se utiliza como contenedor alrededor de un objeto de un determinado tipo T. El contenedor Lazy <T> representa a un proxy directo para una instancia de la clase que no exista todavía. Hay muchas razones para utilizar estos contenedores diferidos, el más importante de la que mejora el rendimiento. Con la inicialización relajada de los objetos, se evita cualquier cálculo que no es estrictamente necesario, por lo que reduce el consumo de memoria. Si se aplica de forma adecuada, diferida creación de instancias de objetos también puede ser una herramienta de enormes para que las aplicaciones de inicio con mayor rapidez. El código siguiente muestra cómo inicializar un objeto de una forma diferida:

var container = new Lazy<DataContainer>();

En este ejemplo, la clase DataContainer indica un objeto contenedor de datos sin formato que se hace referencia a las matrices de otros objetos. Justo después de invocar el operador new en una instancia de Lazy <T>, todo lo que devuelve es una instancia activa de la clase Lazy <T>; en caso de no va a contener una instancia del tipo especificado T. Si es necesario pasar una instancia de DataContainer a los miembros de otras clases, debe cambiar la firma de estos miembros utilizar Lazy <DataContainer>, similar al siguiente:

void ProcessData(Lazy<DataContainer> container);

¿Cuándo se la instancia real de DataContainer crea para que el programa puede funcionar con los datos que necesita? Let’s, eche un vistazo a la interfaz de programación pública de la clase Lazy <T>. La interfaz pública es bastante ligero que incluya sólo dos propiedades: Valor y IsValue ­ creados. La propiedad Value devuelve el valor actual de la instancia asociada con el tipo diferido, si hay alguno. La propiedad se define como sigue:

public T Value 
{
  get { ... }
}

La propiedad IsValueCreated devuelve un valor de tipo Boolean y que indica si se ha creado una instancia del tipo diferido. Éste es un extracto de su código fuente:

public bool IsValueCreated
{
  get
  {
    return ((m_boxed != null) && (m_boxed is Boxed<T>));
  }
}

El miembro m_boxed es un miembro interno de volátil y privado de la clase Lazy <T> que contiene la instancia real del tipo T, si existe. Por lo tanto, IsValueCreated simplemente comprueba si existe una instancia activa de T y que devuelve una respuesta de tipo Boolean. Como se ha mencionado, el miembro de m_boxed es privada y volátil, como se muestra en este fragmento de código:

private volatile object m_boxed;

En C#, la palabra clave volatile indica a un miembro que se puede modificar mediante un subproceso en ejecución al mismo tiempo. La palabra clave volatile se utiliza para los miembros que están disponibles en un entorno multiproceso, pero falta (fundamentalmente por razones de rendimiento) ninguna protección contra posibles subprocesos concurrentes que se puede obtener acceso a dichos miembros al mismo tiempo. Volverá a los aspectos de subprocesamiento de Lazy <T> más adelante. Por ahora, basta con decir que los miembros públicos y protegidos de Lazy <T> son subprocesos de forma predeterminada. La instancia real del tipo T se crea la primera vez que cualquier código que intenta obtener acceso al miembro de valor. Los detalles de la creación de objetos dependen de los atributos de subprocesamiento especificados opcionalmente mediante el constructor Lazy <T>. Debe quedar claro que las implicaciones del modo de subprocesamiento sólo son importantes para cuando el valor con conversión boxing es realmente inicializado o tener acceso a la primera vez.

En el caso predeterminado, una instancia del tipo T se obtiene a través de reflexión colocando una llamada a Activator.CreateInstance. A continuación, presentamos un ejemplo rápido de la interacción típica con el tipo de Lazy <T>:

var temp = new Lazy<DataContainer>();
Console.WriteLine(temp.IsValueCreated);
Console.WriteLine(temp.Value.SomeValue);

Tenga en cuenta que no es estrictamente necesario para comprobar si hay IsValueCreated antes de invocar el valor. Suele recurrir a comprobar el valor de IsValueCreated sólo si, por alguna razón, desea saber si un valor está asociado actualmente con el tipo diferido. No hay ninguna necesidad de comprobar IsValueCreated para evitar una excepción de referencia nula en el valor. El código siguiente funciona correctamente:

var temp = new Lazy<DataContainer>();
Console.WriteLine(temp.Value.SomeValue);

El captador de la propiedad Value se comprueba si ya existe un valor con conversión boxing; en caso contrario, desencadena la lógica que se crea una instancia del tipo de contenido y devuelve.

El proceso de creación de instancias

Por supuesto, si escribe en el constructor de la Lazy: DataContainer en el ejemplo anterior, se inicia una excepción, el código es responsable de administrar dicha excepción. La excepción capturada es de tipo TargetInvocationException; la excepción típica que se obtiene cuando se produce un error en la reflexión de .NET crear una instancia de un tipo de forma indirecta.

La lógica de contenedor Lazy <T> simplemente garantiza que se crea una instancia del tipo T; no también garantiza que no recibirá una excepción de referencia nula como tener acceso a cualquiera de los miembros públicos en T. Por ejemplo, considere el fragmento de código siguiente:

public class DataContainer
{
  public DataContainer()
  {
  }

  public IList<String> SomeValues { get; set; }
}

Imagine ahora que se intenta llamar a este código desde un programa de cliente:

var temp = new Lazy<DataContainer>();
Console.WriteLine(temp.Value.SomeValues.Count);

En este caso, podrá obtener una excepción porque la propiedad SomeValues del objeto DataContainer es null, no porque la DataContainer null por sí mismo. La excepción se produce porque constructor del DataContainer no inicializa correctamente todos sus miembros; el error no tiene nada que hacer con la implementación del enfoque diferida.

La propiedad Value de Lazy <T> es una propiedad de sólo lectura, lo que significa que una vez inicializado, un objeto Lazy <T> devuelve siempre la misma instancia del tipo T o el mismo valor si T es un tipo de valor. No se puede modificar la instancia, pero puede tener acceso a las propiedades públicas que puede tener la instancia.

A continuación, le mostramos cómo configurar un objeto Lazy <T> para pasar parámetros ad hoc para el tipo de T:

temp = new Lazy<DataContainer>(() => new Orders(10));

Uno de los constructores de Lazy <T> toma a un delegado a través del cual se puede especificar cualquier acción necesaria para producir datos de entrada correctas para el constructor de T. El delegado no se ejecutará hasta que la propiedad Value del tipo T ajustado que se tiene acceso por primera vez.

Inicialización segura de subprocesos

De forma predeterminada, Lazy <T> es seguro para subprocesos, lo que significa que varios subprocesos pueden tener acceso a un objeto y todos los subprocesos recibirán la misma instancia.
del tipo T. Let’s ver aspectos del subprocesamiento que son importantes sólo para el primer acceso a un objeto diferido.

El primer subproceso para alcanzar el objeto Lazy <T> desencadenará el proceso de creación de instancias de tipo T. Todos los subprocesos siguientes que obtienen acceso al valor de recibirán la respuesta generada por la primera, lo que es. En otras palabras, si el primer subproceso produce una excepción cuando se invoca el constructor del tipo T, a continuación, todas las llamadas siguientes, independientemente del subproceso, recibirá la excepción de la misma.
Por diseño, diferentes subprocesos no pueden obtener respuestas distintas de la misma instancia de Lazy <T>. Éste es el comportamiento que se obtiene cuando se elige el constructor predeterminado de Lazy <T>.

Sin embargo, la clase Lazy <T>, también incluye un constructor adicional:

public Lazy(bool isThreadSafe)

El argumento booleano indica si desea que la seguridad de los subprocesos. Como se ha mencionado, el valor predeterminado es true, que ofrece el comportamiento mencionadas anteriormente.

Si se pasa false de en lugar de ello, se tendrá acceso a la propiedad Value de un solo subproceso, que inicializa el tipo diferido. El comportamiento será indefinido si varios subprocesos intentan obtener acceso a la propiedad Value.

El constructor Lazy <T> que acepta un valor booleano es un caso especial de la firma más general que pasa el Lazy <T>
un valor de la enumeración LazyThreadSafetyMode del constructor. La figura 1, se explica la función de cada valor de la enumeración.

Figura 1 de la enumeración de TheLazyThreadSafetyMode

Valor Descripción
Ninguno La instancia Lazy <T> no es seguro para subprocesos y su comportamiento será indefinido si se tiene acceso a desde varios subprocesos.
PublicationOnly Se permiten que varios subprocesos al mismo tiempo al intentar inicializar el tipo diferido. El primer subproceso para finalizar wins y los resultados generados por todos los demás se descartan.
ExecutionAndPublication Se utilizan bloqueos para asegurarse de que sólo un subproceso puede inicializar una instancia de Lazy <T> de manera segura para subprocesos.

Puede establecer el modo de PublicationOnly con cualquiera de los siguientes constructores:

public Lazy(LazyThreadSafetyMode mode)
public Lazy<T>(Func<T>, LazyThreadSafetyMode mode)

Los valores de del 1 de la figura no sea PublicationOnly se define de forma implícita cuando se utiliza el constructor que acepta un valor de tipo Boolean:

public Lazy(bool isThreadSafe)

En el constructor, si el argumento isThreadSafe es false, a continuación, el modo de subprocesamiento seleccionado es None. Si el argumento isThreadSafe se establece en true de , a continuación, se establece el modo de subprocesamiento para ExecutionAndPublication. ExecutionAndPublication es también el modo de trabajo cuando se elige el constructor predeterminado.

El modo de PublicationOnly se encuentra en algún lugar entre la seguridad de los subprocesos completa garantizada ExecutionAndPublication y la falta de sus que obtiene con ninguno. PublicationOnly permite que los subprocesos simultáneos intentar crear la instancia del tipo T, pero garantiza que sólo un subproceso es el ganador. La instancia de T creada por el ganador, a continuación, se comparte entre todos los demás subprocesos, independientemente de la instancia que cada uno de ellos puede ha calculado.

Hay una diferencia interesante entre ninguno y ExecutionAndPublication con respecto a una posible excepción producida durante la inicialización. Cuando se establece PublicationOnly, no se encuentra en la caché de una excepción generada durante la inicialización; posteriormente, cada subproceso que intenta leer Value tendrá la oportunidad para reinicializar si una instancia de T que no está disponible. Otra diferencia entre PublicationOnly y ninguno es que no se produce ninguna excepción en modo de PublicationOnly si el constructor de T intenta el acceso de forma recursiva valores. Esta situación, producirá una excepción InvalidOperation cuando funciona de la clase Lazy <T> en ninguno o ExecutionAndPublication modos.

Quitando la seguridad de los subprocesos le ofrece una ventaja de rendimiento sin formato, pero debe tener cuidado para evitar errores desagradables y las condiciones de anticipación. Por lo tanto, se recomienda que utilice la opción LazyThreadSafetyMode.None sólo cuando el rendimiento es extremadamente importante.

Si utiliza LazyThreadSafetyMode.None, sigue siendo la responsabilidad de asegurarse de que la instancia Lazy <T> nunca se inicializarán de más de un subproceso. De lo contrario, se pueden incurrir en resultados impredecibles. Si se produce una excepción durante la inicialización, la misma excepción se almacena en caché y se produce para cada acceso posterior al valor en el mismo subproceso.

Inicialización de ThreadLocal

Por diseño, Lazy <T> no permite diferentes subprocesos administrar su propio personal instancia del tipo T. Sin embargo, si desea permitir que
comportamiento, debe optar por una clase diferente, el tipo de ThreadLocal <T>. A continuación, le mostramos cómo usarlo:

var counter = new ThreadLocal<Int32>(() => 1);

El constructor toma a un delegado y utiliza para inicializar la variable local de subprocesos. Cada subproceso tiene sus propios datos que esté completamente fuera del alcance de otros subprocesos. A diferencia de Lazy <T>, la propiedad Value en ThreadLocal <T> es de lectura y escritura. Cada tipo de acceso, por tanto, es independiente de la siguiente y se puede producir resultados diferentes, incluidos iniciar (o no) una excepción. Si no proporciona un delegado de acción mediante el constructor ThreadLocal <T>, el objeto incrustado se inicializa con el valor predeterminado para el tipo, null si T es una clase.

Implementar propiedades diferidas

¿La mayoría de los casos, utiliza Lazy <T> para las propiedades en sus propias clases, pero las clases, exactamente? Carga diferida de sus propios que ofrecen las herramientas ORM, no por lo que si utiliza estas herramientas, el acceso datos capa probablemente es el segmento de la aplicación donde encontrará candidato es probable que las clases a las propiedades de host diferida. Si no utiliza herramientas ORM, la capa de acceso a datos definitivamente es una buena opción para perezosos propiedades.

Segmentos de la aplicación donde se utiliza la inserción de dependencia podrían ser otro buen ajuste de pereza. En el 4 de .NET Framework, el Framework de extensibilidad administradas (MEF) sólo implementa la extensibilidad y la inversión de control mediante Lazy <T>. Incluso si no está utilizando directamente el MEF, administración de dependencias es muy apropiado para diferido de propiedades.

Implementación de una propiedad diferida dentro de una clase no requiere ningún ciencia rocket, como de figura 2 se muestra.

La figura 2 del ejemplo de una propiedad diferida

public class Customer
{
   private readonly Lazy<IList<Order>> orders;

   public Customer(String id)
   {
      orders = new Lazy<IList<Order>>( () =>
      {
         return new List<Order>();
      }
      );
   }

   public IList<Order> Orders
   {
      get
      {
         // Orders is created on first access
         return orders.Value;
      }
   }
}

Llenar un hueco

Ajustar hacia arriba, carga diferida es un concepto abstracto que se hace referencia a la carga de datos sólo cuando realmente se necesita. Hasta el 4 de .NET Framework, los programadores necesitaban para encargarse de desarrollar la lógica de inicialización relajada a sí mismos. La clase Lazy <T> amplía el Toolkit de programación de .NET Framework y ofrece una magnífica oportunidad para evitar el cálculo poco rentable por una instancia de los caros objetos únicamente cuando estrictamente necesarios y comienza un momento antes de su uso.

Dino Esposito es el autor de Programación de ASP.NET MVC de Microsoft Press y coautor de ha Microsoft .NET: Arquitectura de aplicaciones para la empresa(Microsoft Press, 2008). Con residencia en Italia, Esposito participa habitualmente en conferencias y eventos del sector en todo el mundo. Puede participar en su blog en weblogs.asp.net/despos.

Gracias al siguiente experto técnico por su ayuda en la revisión de este artículo: Greg Paperin