Inicialización diferida

La inicialización diferida de un objeto significa que su creación se pospone hasta que se utiliza por primera vez. (En este tema, los términos inicialización diferida y creación de instancias diferida son sinónimos.) La inicialización diferida se utiliza principalmente para mejorar el rendimiento, evitar cálculos innecesarios y reducir los requisitos de memoria de los programas. Estos son los escenarios más comunes:

  • Cuando hay un objeto que resulta costoso crear y existe la posibilidad de que el programa no lo utilice. Por ejemplo, supongamos que tiene en memoria un objeto Customer con una propiedad Orders que contiene una gran matriz de objetos Order que, para inicializarse, requiere una conexión a una base de datos. Si el usuario nunca solicita que se muestren los pedidos ni se utilicen los datos en un cálculo, no existe ninguna razón para utilizar la memoria del sistema o los ciclos de cálculo para crearla. Si se utiliza Lazy<Orders> para declarar el objeto Orders para su inicialización diferida, se puede evitar malgastar los recursos del sistema cuando no se utiliza el objeto.

  • Cuando crear un objeto resulta costoso y se desea diferir su creación hasta que se hayan completado otras operaciones costosas. Por ejemplo, supongamos que el programa carga varias instancias de objetos al iniciarse, pero que solo algunas de ellas se necesitan inmediatamente. Puede mejorar el rendimiento de inicio del programa difiriendo la inicialización de los objetos que no se requieren hasta que se hayan creado los objetos necesarios.

Aunque puede escribir su propio código para realizar la inicialización diferida, recomendamos utilizar Lazy<T> en su lugar. Lazy<T> y sus tipos relacionados también admiten la seguridad para subprocesos y proporcionan una directiva de propagación de excepciones coherente.

En la siguiente tabla se muestra una lista de los tipos que se proporcionan en la versión 4 de .NET Framework para habilitar la inicialización diferida en distintos escenarios.

Tipo

Descripción

[ T:System.Lazy`1 ]

Clase contenedora que proporciona semántica de inicialización diferida para cualquier biblioteca de clases o tipo definido por el usuario.

[ T:System.Threading.ThreadLocal`1 ]

Se parece a Lazy<T>, con la diferencia de que proporciona la semántica de inicialización diferida para cada subproceso local. Cada subproceso tiene el acceso a su propio valor único.

[ T:System.Threading.LazyInitializer ]

Proporciona métodos static avanzados (Shared en Visual Basic) para la inicialización diferida de objetos sin la sobrecarga de una clase.

Inicialización diferida básica

Para definir un tipo inicializado diferido, por ejemplo, MyType, utilice Lazy<MyType> (Lazy(Of MyType) en Visual Basic), como se muestra en el siguiente ejemplo. Si no se pasa ningún delegado en el constructor Lazy<T>, el tipo ajustado se crea mediante Activator.CreateInstance la primera vez que se tiene acceso a la propiedad de valor. Si el tipo no tiene un constructor predeterminado, se produce una excepción en tiempo de ejecución.

En el siguiente ejemplo, supongamos que Orders es una clase que contiene una matriz de objetos Order recuperada de una base de datos. Un objeto Customer contiene una instancia de Orders, pero dependiendo de las acciones del usuario, puede que no se necesiten los datos del objeto Orders.

' Initialize by using default Lazy<T> constructor. The 
'Orders array itself is not created yet.
Dim _orders As Lazy(Of Orders) = New Lazy(Of Orders)()
// Initialize by using default Lazy<T> constructor. The 
// Orders array itself is not created yet.
Lazy<Orders> _orders = new Lazy<Orders>();

También puede pasar un delegado en el constructor Lazy<T> que invoque una sobrecarga del constructor concreta en el tipo ajustado en el momento de la creación, y realizar los demás pasos de inicialización que se necesiten, como se muestra en el siguiente ejemplo.

' Initialize by invoking a specific constructor on Order 
' when Value property is accessed
Dim _orders As Lazy(Of Orders) = New Lazy(Of Orders)(Function() New Orders(100))
// Initialize by invoking a specific constructor on Order when Value
// property is accessed
Lazy<Orders> _orders = new Lazy<Orders>(() => new Orders(100));

Una vez creado el objeto Lazy, no se crea ninguna instancia de Orders hasta que se obtiene acceso por primera vez a la propiedad Value de la variable Lazy. La primera vez que se tiene acceso a ella, se crea y devuelve el tipo ajustado, y se almacena para cualquier acceso futuro.

' We need to create the array only if _displayOrders is true
If _displayOrders = True Then
    DisplayOrders(_orders.Value.OrderData)
Else
    ' Don't waste resources getting order data.
End If
// We need to create the array only if displayOrders is true
if (displayOrders == true)
{
    DisplayOrders(_orders.Value.OrderData);
}
else
{
    // Don't waste resources getting order data.
}

Un objeto Lazy<T> siempre devuelve el mismo objeto o valor con el que se inicializó. Por consiguiente, la propiedad Value es de solo lectura. Si Value almacena un tipo de referencia, no se le puede asignar un nuevo objeto. (Sin embargo, se puede cambiar el valor de sus campos y propiedades públicos que se pueden establecer.) Si Value almacena un tipo de valor, su valor no se puede modificar. No obstante, puede crear una nueva variable invocando de nuevo el constructor de la variable con nuevos argumentos.

_orders = New Lazy(Of Orders)(Function() New Orders(10))
_orders = new Lazy<Orders>(() => new Orders(10));

La nueva instancia diferida, al igual que la anterior, no crea ninguna instancia de Orders hasta que se tiene acceso a su propiedad Value por primera vez.

Inicialización segura para subprocesos

De forma predeterminada, los objetos Lazy<T> son seguros para subprocesos. Es decir, si el constructor no especifica el tipo de seguridad para subprocesos, los objetos Lazy<T> que cree serán seguros para subprocesos. En escenarios multiproceso, el primer subproceso que tiene acceso a la propiedad Value de un objeto Lazy<T> seguro para subprocesos la inicializa para todos los accesos subsiguientes en todos los subprocesos y todos los subprocesos comparten los mismos datos. Por consiguiente, no importa qué subproceso inicializa el objeto y las condiciones de carrera son benignas.

NotaNota

Puede extender esta coherencia a las condiciones de error mediante el almacenamiento en caché de excepciones.Para obtener más información, vea la siguiente sección Excepciones en los objetos diferidos.

En el ejemplo siguiente se muestra que la misma instancia de Lazy<int> tiene el mismo valor para tres subprocesos independientes.

' Initialize the integer to the managed thread id of the 
' first thread that accesses the Value property.
Dim number As Lazy(Of Integer) = New Lazy(Of Integer)(Function()
                                                          Return Thread.CurrentThread.ManagedThreadId
                                                      End Function)

Dim t1 As New Thread(Sub()
                         Console.WriteLine("number on t1 = {0} threadID = {1}",
                                           number.Value, Thread.CurrentThread.ManagedThreadId)
                     End Sub)
t1.Start()

Dim t2 As New Thread(Sub()
                         Console.WriteLine("number on t2 = {0} threadID = {1}",
                                           number.Value, Thread.CurrentThread.ManagedThreadId)
                     End Sub)
t2.Start()

Dim t3 As New Thread(Sub()
                         Console.WriteLine("number on t3 = {0} threadID = {1}",
                                           number.Value, Thread.CurrentThread.ManagedThreadId)
                     End Sub)
t3.Start()

' Ensure that thread IDs are not recycled if the 
' first thread completes before the last one starts.
t1.Join()
t2.Join()
t3.Join()

' Sample Output:
'       number on t1 = 11 ThreadID = 11
'       number on t3 = 11 ThreadID = 13
'       number on t2 = 11 ThreadID = 12
'       Press any key to exit.
// Initialize the integer to the managed thread id of the 
// first thread that accesses the Value property.
Lazy<int> number = new Lazy<int>(() => Thread.CurrentThread.ManagedThreadId);

Thread t1 = new Thread(() => Console.WriteLine("number on t1 = {0} ThreadID = {1}",
                                        number.Value, Thread.CurrentThread.ManagedThreadId));
t1.Start();

Thread t2 = new Thread(() => Console.WriteLine("number on t2 = {0} ThreadID = {1}",
                                        number.Value, Thread.CurrentThread.ManagedThreadId));
t2.Start();

Thread t3 = new Thread(() => Console.WriteLine("number on t3 = {0} ThreadID = {1}", number.Value,
                                        Thread.CurrentThread.ManagedThreadId));
t3.Start();

// Ensure that thread IDs are not recycled if the 
// first thread completes before the last one starts.
t1.Join();
t2.Join();
t3.Join();

/* Sample Output:
    number on t1 = 11 ThreadID = 11
    number on t3 = 11 ThreadID = 13
    number on t2 = 11 ThreadID = 12
    Press any key to exit.
*/

Si necesita datos independientes en cada subproceso, utilice el tipo ThreadLocal<T>, como se describe más adelante en este tema.

Algunos constructores Lazy<T> tienen un parámetro booleano denominado isThreadSafe que se utiliza para especificar si se va a obtener acceso a la propiedad Value desde varios subprocesos. Si piensa tener acceso a la propiedad desde un solo subproceso, pase false para obtener una ventaja de rendimiento modesta. Si piensa obtener acceso a la propiedad desde varios subprocesos, pase true para indicar a la instancia de Lazy<T> cómo controlar correctamente las condiciones de carrera en las que un subproceso producirá una excepción en el momento de la inicialización.

Algunos constructores Lazy<T> tienen un parámetro de LazyThreadSafetyMode denominado mode. Estos constructores proporcionan un modo adicional de seguridad para subprocesos. En la siguiente tabla, se muestra cómo la seguridad para subprocesos de un objeto Lazy<T> se ve afectada por los parámetros de constructor que especifican la seguridad para subprocesos. Cada constructor tiene como máximo un parámetro de este tipo.

Seguridad para subprocesos del objeto

Parámetro mode de LazyThreadSafetyMode

Parámetro booleano isThreadSafe

Ningún parámetro de seguridad para subprocesos

Totalmente seguro para subprocesos; solo un subproceso a la vez intenta inicializar el valor.

[ F:System.Threading.LazyThreadSafetyMode.ExecutionAndPublication ]

true

Sí.

No es seguro para subprocesos.

[ F:System.Threading.LazyThreadSafetyMode.None ]

false

No es aplicable

Totalmente seguro para subprocesos; los subprocesos se precipitan a inicializar el valor.

[ F:System.Threading.LazyThreadSafetyMode.PublicationOnly ]

No es aplicable

No es aplicable

Tal y como se muestra en la tabla, especificar LazyThreadSafetyMode.ExecutionAndPublication para el parámetro mode equivale a especificar true para el parámetro isThreadSafe, y especificar LazyThreadSafetyMode.None equivale a especificar false.

Si se especifica LazyThreadSafetyMode.PublicationOnly, varios subprocesos podrán intentar inicializar la instancia de Lazy<T>. Solo un subproceso podrá ganar esta carrera y todos los demás subprocesos recibirán el valor inicializado por el subproceso ganador. Si se produce una excepción en un subproceso durante la inicialización, ese subproceso no recibirá el valor establecido por el subproceso ganador. Las excepciones no se almacenan en la memoria caché, por lo que la próxima vez que se intente obtener acceso a la propiedad Value, es posible que la inicialización se realice correctamente. Esto difiere de la manera en que se tratan las excepciones en otros modos, lo cual se describe en la siguiente sección. Para obtener más información, vea la enumeración LazyThreadSafetyMode.

Excepciones en los objetos diferidos

Como se ha explicado anteriormente, un objeto Lazy<T> siempre devuelve el mismo objeto o valor con que se inicializó y, por consiguiente, la propiedad Value es de solo lectura. Si habilita el almacenamiento en caché de excepciones, esta inmutabilidad también se extiende al comportamiento de las excepciones. Si un objeto con inicialización diferida tiene habilitado el almacenamiento en caché de excepciones y produce una excepción desde su método de inicialización cuando se tiene acceso por primera vez a la propiedad Value, esta misma excepción se producirá cada vez que se intente tener acceso a la propiedad Value. En otras palabras, nunca se vuelve a invocar el constructor del tipo encapsulado, ni siquiera en escenarios multiproceso. Por tanto, el objeto Lazy<T> no puede producir una excepción en un acceso y devolver un valor en un acceso posterior.

El almacenamiento en caché de excepciones se habilita cuando se usa cualquier constructor System.Lazy<T> que toma un método de inicialización (parámetro valueFactory); por ejemplo, se habilita al usar el constructor Lazy(T)(Func(T)). Si el constructor también toma un valor LazyThreadSafetyMode (parámetro mode), especifique LazyThreadSafetyMode.None o LazyThreadSafetyMode.ExecutionAndPublication. Al especificar un método de inicialización se habilita el almacenamiento en caché de excepciones para estos dos modos. El método de inicialización puede ser muy simple. Por ejemplo, puede llamar al constructor predeterminado para T: new Lazy<Contents>(() => new Contents(), mode) en C# o New Lazy(Of Contents)(Function() New Contents()) en Visual Basic. Si usa un constructor System.Lazy<T> que no especifica ningún método de inicialización, las excepciones producidas por el constructor predeterminado de T no se almacenan en memoria caché. Para obtener más información, vea la enumeración LazyThreadSafetyMode.

NotaNota

Si crea un objeto Lazy<T> con el parámetro de constructor isThreadSafe establecido en false o el parámetro de constructor mode establecido en LazyThreadSafetyMode.None, debe tener acceso al objeto Lazy<T> desde un único subproceso o especificar su propia sincronización.Esto se aplica a todos los aspectos del objeto, incluido el almacenamiento en caché de excepciones.

Tal y como se menciona en la sección anterior, los objetos Lazy<T> que se crean especificando LazyThreadSafetyMode.PublicationOnly tratan las excepciones de manera diferente. Con PublicationOnly, varios subprocesos pueden competir para inicializar la instancia de Lazy<T>. En este caso, las excepciones no se almacenan en la memoria caché y se puede seguir intentando obtener acceso a la propiedad Value hasta que la inicialización se realice correctamente.

En la tabla siguiente se resume la forma en que los constructores Lazy<T> controlan el almacenamiento en caché de excepciones.

Constructor

Modo de seguridad para subprocesos

Usa método de inicialización

Se almacenan en caché las excepciones

Lazy(T)()

(ExecutionAndPublication)

No

No

Lazy(T)(Func(T))

(ExecutionAndPublication)

Lazy(T)(Boolean)

True (ExecutionAndPublication) o false (None)

No

No

Lazy(T)(Func(T), Boolean)

True (ExecutionAndPublication) o false (None)

Lazy(T)(LazyThreadSafetyMode)

Especificado por el usuario

No

No

Lazy(T)(Func(T), LazyThreadSafetyMode)

Especificado por el usuario

No si el usuario especifica PublicationOnly; de lo contrario, sí.

Implementar una propiedad con inicialización diferida

Para implementar una propiedad pública mediante la inicialización diferida, defina el campo de respaldo de la propiedad en Lazy<T> y devuelva la propiedad Value del descriptor de acceso get de la propiedad.

Class Customer
    Private _orders As Lazy(Of Orders)
    Public Shared CustomerID As String
    Public Sub New(ByVal id As String)
        CustomerID = id
        _orders = New Lazy(Of Orders)(Function()
                                          ' You can specify additional 
                                          ' initialization steps here
                                          Return New Orders(CustomerID)
                                      End Function)

    End Sub
    Public ReadOnly Property MyOrders As Orders

        Get
            Return _orders.Value
        End Get

    End Property

End Class
class Customer
{
    private Lazy<Orders> _orders;
    public string CustomerID {get; private set;}
    public Customer(string id)
    {
        CustomerID = id;
        _orders = new Lazy<Orders>(() =>
        {
            // You can specify any additonal 
            // initialization steps here.
            return new Orders(this.CustomerID);
        });
    }

    public Orders MyOrders
    {
        get
        {
            // Orders is created on first access here.
            return _orders.Value;
        }
    }
}

La propiedad Value es de solo lectura; por consiguiente, la propiedad que la expone no tiene ningún descriptor de acceso set. Si necesita que un objeto Lazy<T> respalde una propiedad de lectura y escritura, el descriptor de acceso set debe crear un nuevo objeto Lazy<T> y asignarlo al dispositivo de copia de seguridad. El descriptor de acceso set debe crear una expresión lambda que devuelva el nuevo valor de propiedad que se pasó al descriptor de acceso set y debe pasar esa expresión lambda al constructor del nuevo objeto Lazy<T>. La próxima vez que se obtenga acceso a la propiedad Value, se inicializará el nuevo objeto Lazy<T> y, a continuación, su propiedad Value devolverá el nuevo valor que se asignó a la propiedad. El motivo de este complejo proceso reside en el mantenimiento de las protecciones multithreading integradas en Lazy<T>. De lo contrario, los descriptores de acceso deberán almacenar en memoria caché el primer valor devuelto por la propiedad Value y modificar únicamente dicho valor; para ello, el usuario deberá escribir su propio código seguro para subprocesos. Debido a las inicializaciones adicionales que requiere una propiedad de lectura y escritura respaldada por un objeto Lazy<T>, el rendimiento podría no ser aceptable. Además, según el escenario, es posible que sea necesaria una mayor coordinación para evitar condiciones de carrera entre los establecedores y los captadores.

Inicialización diferida local de subprocesos

En algunos escenarios multiproceso, puede que sea conveniente dar a cada subproceso sus propios datos privados. Estos datos se denominan datos locales de subprocesos. En la versión 3.5 de .NET Framework y en las versiones anteriores, se podría aplicar el atributo ThreadStatic a una variable estática para que fuese local de subprocesos. Sin embargo, utilizar el atributo ThreadStatic puede dar lugar a errores sutiles. Por ejemplo, incluso instrucciones de inicialización básicas harán se inicialice la variable únicamente en el primer subproceso que tenga acceso a ella, como se muestra en el siguiente ejemplo.

<ThreadStatic()>
Shared counter As Integer
[ThreadStatic]
static int counter = 1;

En todos los otros subprocesos, la variable se inicializará mediante su valor predeterminado (cero). Como alternativa en la versión 4 de .NET Framework, puede utilizar el tipo System.Threading.ThreadLocal<T> para crear una variable basada en instancia y local de subprocesos que se inicializa en todos los subprocesos por el delegado Action<T> que le proporcione. En el siguiente ejemplo, todos los subprocesos que tienen acceso a counter verán el valor de inicio 1.

Dim betterCounter As ThreadLocal(Of Integer) = New ThreadLocal(Of Integer)(Function() 1)
ThreadLocal<int> betterCounter = new ThreadLocal<int>(() => 1);

ThreadLocal<T> se ajusta a su objeto de un modo muy similar a Lazy<T>, con estas diferencias esenciales:

  • Cada subproceso inicializa la variable local de subprocesos mediante sus propios datos privados, a los que no se puede tener acceso desde otros subprocesos.

  • La propiedad ThreadLocal<T>.Value es de lectura y escritura y se puede modificar tantas veces como se desee. Esto puede afectar a la propagación de excepciones; por ejemplo, una operación get podría producir una excepción y, la siguiente, inicializar el valor correctamente.

  • Si no se proporciona ningún delegado de inicialización, ThreadLocal<T> inicializará su tipo ajustado utilizando el valor predeterminado del tipo. En este sentido, ThreadLocal<T> es coherente con el atributo ThreadStaticAttribute.

El siguiente ejemplo muestra que cada subproceso que tiene acceso a la instancia de ThreadLocal<int> obtiene su propia copia singular de los datos.

' Initialize the integer to the managed thread id on a per-thread basis.
Dim threadLocalNumber As New ThreadLocal(Of Integer)(Function() Thread.CurrentThread.ManagedThreadId)
Dim t4 As New Thread(Sub()
                         Console.WriteLine("number on t4 = {0} threadID = {1}",
                                           threadLocalNumber.Value, Thread.CurrentThread.ManagedThreadId)
                     End Sub)
t4.Start()

Dim t5 As New Thread(Sub()
                         Console.WriteLine("number on t5 = {0} threadID = {1}",
                                           threadLocalNumber.Value, Thread.CurrentThread.ManagedThreadId)
                     End Sub)
t5.Start()

Dim t6 As New Thread(Sub()
                         Console.WriteLine("number on t6 = {0} threadID = {1}",
                                           threadLocalNumber.Value, Thread.CurrentThread.ManagedThreadId)
                     End Sub)
t6.Start()

' Ensure that thread IDs are not recycled if the 
' first thread completes before the last one starts.
t4.Join()
t5.Join()
t6.Join()

'Sample(Output)
'      threadLocalNumber on t4 = 14 ThreadID = 14 
'      threadLocalNumber on t5 = 15 ThreadID = 15
'      threadLocalNumber on t6 = 16 ThreadID = 16 
// Initialize the integer to the managed thread id on a per-thread basis.
ThreadLocal<int> threadLocalNumber = new ThreadLocal<int>(() => Thread.CurrentThread.ManagedThreadId);
Thread t4 = new Thread(() => Console.WriteLine("threadLocalNumber on t4 = {0} ThreadID = {1}",
                                    threadLocalNumber.Value, Thread.CurrentThread.ManagedThreadId));
t4.Start();

Thread t5 = new Thread(() => Console.WriteLine("threadLocalNumber on t5 = {0} ThreadID = {1}",
                                    threadLocalNumber.Value, Thread.CurrentThread.ManagedThreadId));
t5.Start();

Thread t6 = new Thread(() => Console.WriteLine("threadLocalNumber on t6 = {0} ThreadID = {1}",
                                    threadLocalNumber.Value, Thread.CurrentThread.ManagedThreadId));
t6.Start();

// Ensure that thread IDs are not recycled if the 
// first thread completes before the last one starts.
t4.Join();
t5.Join();
t6.Join();

/* Sample Output:
   threadLocalNumber on t4 = 14 ThreadID = 14 
   threadLocalNumber on t5 = 15 ThreadID = 15
   threadLocalNumber on t6 = 16 ThreadID = 16 
*/

Variables locales de subprocesos de Parallel.For y ForEach

Cuando se utiliza el método Parallel.For o el método Parallel.ForEach para recorrer en iteración los orígenes de datos en paralelo, se pueden utilizar las sobrecargas con compatibilidad integrada para los datos locales de subprocesos. En estos métodos, el carácter local de subprocesos se logra utilizando delegados locales para crear los datos, tener acceso a ellos y limpiarlos. Para obtener más información, vea Cómo: Escribir un bucle Parallel.For que tenga variables locales de subproceso y Cómo: Escribir un bucle Parallel.ForEach que tenga variables locales de subproceso.

Utilizar la inicialización diferida para escenarios de sobrecarga baja

En los escenarios donde se tiene que inicializar en diferido un número grande de objetos, podría decidir que ajustar cada objeto en un objeto Lazy<T> exige demasiada memoria o demasiados recursos de cálculo. O bien, puede suceder que tenga requisitos estrictos sobre cómo se expone la inicialización diferida. En casos como este, puede utilizar los métodos staticShared en Visual Basic) de la clase System.Threading.LazyInitializer para inicializar en diferido cada objeto sin ajustarlo en una instancia de Lazy<T>.

En el siguiente ejemplo, supongamos que, en lugar de ajustar un objeto Orders completo en un objeto Lazy<T>, los objetos Order individuales se inicializan en diferido solamente si es necesario.

' Assume that _orders contains null values, and
' we only need to initialize them if displayOrderInfo is true
If displayOrderInfo = True Then


    For i As Integer = 0 To _orders.Length
        ' Lazily initialize the orders without wrapping them in a Lazy(Of T)
        LazyInitializer.EnsureInitialized(_orders(i), Function()
                                                          ' Returns the value that will be placed in the ref parameter.
                                                          Return GetOrderForIndex(i)
                                                      End Function)
    Next
End If
// Assume that _orders contains null values, and
// we only need to initialize them if displayOrderInfo is true
if(displayOrderInfo == true)
{
    for (int i = 0; i < _orders.Length; i++)
    {
        // Lazily initialize the orders without wrapping them in a Lazy<T>
        LazyInitializer.EnsureInitialized(ref _orders[i], () =>
            {
                // Returns the value that will be placed in the ref parameter.
                return GetOrderForIndex(i);
            });
    }
}

En este ejemplo, observe que el procedimiento de inicialización se invoca en cada iteración del bucle. En escenarios de varios subprocesos, el primer subproceso que invoca el procedimiento de inicialización es aquel cuyo valor ven todos los subprocesos. Los subprocesos posteriores también invocan el procedimiento de inicialización, pero sus resultados no se utilizan. Si este tipo de condición de carrera potencial no es aceptable, utilice la sobrecarga de LazyInitializer.EnsureInitialized, que toma un argumento Boolean y un objeto de sincronización.

Vea también

Tareas

Cómo: Realizar la inicialización diferida de objetos

Conceptos

Subprocesos y subprocesamiento

Task Parallel Library

Otros recursos

Principios básicos del subprocesamiento administrado

Historial de cambios

Fecha

Historial

Motivo

Marzo de 2011

Información corregida sobre el almacenamiento en caché de excepciones.

Corrección de errores de contenido.

1 de abril de 2011

Otras revisiones a información sobre el almacenamiento en caché de excepciones.

Corrección de errores de contenido.

1 de abril de 2011

Corrección: La llamada a Lazy<T>.ToString no provoca la inicialización.

Corrección de errores de contenido.