Share via


Initialisation tardive

L'initialisation tardive d'un objet signifie que sa création est différée jusqu'à sa première utilisation. (Pour cette rubrique, les termes initialisation tardive et instanciation tardive sont synonymes.) L'initialisation tardive est principalement utilisée pour améliorer les performances, éviter les excès de calcul et réduire les exigences en mémoire des programmes. Voici les scénarios les plus courants :

  • Lorsque vous possédez un objet difficile à créer et que le programme risque de ne pas l'utiliser. Par exemple, supposez que vous avez en mémoire un objet Customer qui possède une propriété Orders contenant un grand tableau d'objets Order qui, pour être initialisé, requiert une connexion de base de données. Si l'utilisateur ne demande jamais d'afficher les commandes ou d'utiliser les données dans un calcul, il n'y a aucune raison d'utiliser la mémoire système ou les cycles de calcul pour le créer. En utilisant Lazy<Orders> pour déclarer l'objet Orders pour une initialisation tardive, vous pouvez éviter de gaspiller des ressources système lorsque l'objet n'est pas utilisé.

  • Lorsque vous disposez d'un objet difficile à créer et que vous souhaitez différer sa création après la réalisation d'autres opérations complexes. Par exemple, supposez que votre programme charge plusieurs instances de l'objet lorsqu'il démarre, mais que seules certaines sont requises immédiatement. Vous pouvez améliorer les performances de démarrage du programme en différant l'initialisation des objets non requis après la création des objets obligatoires.

Bien que vous puissiez écrire votre propre code pour effectuer l'initialisation tardive, nous vous recommandons d'utiliser plutôt Lazy<T>. Lazy<T> et ses types associés prennent également en charge la sécurité des threads et fournissent une stratégie cohérente de propagation des exceptions.

Le tableau suivant répertorie les types que le .NET Framework version 4 fournit pour permettre l'initialisation tardive dans différents scénarios.

Type

Description

[ T:System.Lazy`1 ]

Classe wrapper qui fournit une sémantique d'initialisation tardive pour toute bibliothèque de classes ou tout type défini par l'utilisateur.

[ T:System.Threading.ThreadLocal`1 ]

Ressemble à Lazy<T> mais fournit une sémantique d'initialisation tardive sur une base de thread local. Chaque thread a accès à sa propre valeur unique.

[ T:System.Threading.LazyInitializer ]

Fournit des méthodes static (Shared en Visual Basic) avancées pour l'initialisation tardive des objets sans la charge d'une classe.

Initialisation tardive de base

Pour définir un type initié tardivement, par exemple, MyType, utilisez Lazy<MyType> (Lazy(Of MyType) en Visual Basic), comme indiqué dans l'exemple suivant. Si aucun délégué n'est passé dans le constructeur Lazy<T>, le type inclus dans un wrapper est créé à l'aide de Activator.CreateInstance lorsque la propriété de valeur fait l'objet d'un premier accès. Si le type ne possède pas de constructeur par défaut, une exception runtime est levée.

Dans l'exemple suivant, supposez que Orders est une classe qui contient un tableau d'objets Order extraits d'une base de données. Un objet Customer contient une instance de Orders, mais les données de l'objet Orders peuvent ne pas être requises, en fonction des actions utilisateur.

' 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>();

Vous pouvez également passer un délégué dans le constructeur Lazy<T> qui appelle une surcharge de constructeur spécifique sur le type inclus dans un wrapper au moment de la création, et effectuer toutes les autres étapes d'initialisation requises, comme indiqué dans l'exemple suivant.

' 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));

Une fois l'objet tardif créé, aucune instance de Orders n'est créée jusqu'à ce que la propriété Value de la variable tardive ne fasse l'objet d'un premier accès. Au premier accès, le type inclus dans un wrapper est créé et retourné, puis stocké pour tout accès futur.

' 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 objet Lazy<T> retourne toujours le même objet (ou la même valeur) avec lequel il a été initialisé. Par conséquent, la propriété Value est en lecture seule. Si Value stocke un type référence, vous ne pouvez pas lui assigner de nouvel objet. (Toutefois, vous pouvez modifier la valeur de ses champs publics et propriétés définissables.) Si Value stocke un type valeur, vous ne pouvez pas modifier sa valeur. Néanmoins, vous pouvez créer une variable en appelant à nouveau le constructeur de variable à l'aide des nouveaux arguments.

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

La nouvelle instance tardive, comme celle d'avant, n'instancie pas Orders tant que sa propriété Value ne fait pas l'objet d'un accès.

Initialisation thread-safe

Par défaut, les objets Lazy<T> sont thread-safe. Autrement dit, si le constructeur ne spécifie pas le type de cohérence de thread, les objets Lazy<T> qu'il crée sont thread-safe. Dans les scénarios multithreads, le premier thread qui accède à la propriété Value d'un objet Lazy<T> thread-safe l'initialise pour tous les accès suivants sur tous les threads et tous les threads partagent les mêmes données. Par conséquent, le thread qui initialise l'objet n'a pas d'importance, et les conditions de concurrence sont bénignes.

RemarqueRemarque

Vous pouvez étendre cette cohérence aux conditions d'erreur à l'aide de la mise en cache d'exceptions.Consultez la section suivante, Exceptions des objets tardifs pour plus d'informations.

L'exemple suivant montre que la même instance de Lazy<int> a la même valeur pour trois threads séparés.

' 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 vous avez besoin de données distinctes sur chaque thread, utilisez le type ThreadLocal<T>, comme décrit ultérieurement dans cette rubrique.

Certains constructeurs Lazy<T> disposent d'un paramètre booléen nommé isThreadSafe utilisé pour indiquer si la propriété Value fait l'objet d'accès à partir de plusieurs threads. Si vous projetez d'accéder à la propriété à partir d'un seul thread, passez false de façon à profiter d'un petit avantage en matière de performances. Si vous prévoyez d'accéder à la propriété à partir de plusieurs threads, passez true pour indiquer à l'instance Lazy<T> de gérer correctement les conditions de concurrence dans lesquelles un thread lève une exception au moment de l'initialisation.

Certains constructeurs Lazy<T> ont un paramètre LazyThreadSafetyMode nommé mode. Ces constructeurs fournissent un mode de cohérence de thread supplémentaire. Le tableau suivant montre la manière dont la cohérence de thread d'un objet Lazy<T> est affectée par les paramètres du constructeur qui spécifient la cohérence de thread. Chaque constructeur a au maximum un de ces paramètres.

Cohérence de thread de l'objet

Paramètre LazyThreadSafetyMode mode

Paramètre isThreadSafe booléen

Aucun paramètre de cohérence de thread

Intégralement thread-safe, un seul thread à la fois tente d'initialiser la valeur.

[ F:System.Threading.LazyThreadSafetyMode.ExecutionAndPublication ]

true

Oui.

N'est pas thread-safe.

[ F:System.Threading.LazyThreadSafetyMode.None ]

false

Non applicable.

Intégralement thread-safe, les threads sont en concurrence pour initialiser la valeur.

[ F:System.Threading.LazyThreadSafetyMode.PublicationOnly ]

Non applicable.

Non applicable.

Comme indiqué dans le tableau, spécifier LazyThreadSafetyMode.ExecutionAndPublication pour le paramètre mode revient à spécifier la valeur true pour le paramètre isThreadSafe et spécifier LazyThreadSafetyMode.None revient à spécifier la valeur false.

La spécification de LazyThreadSafetyMode.PublicationOnly permet à plusieurs threads d'essayer d'initialiser l'instance Lazy<T>. Un seul thread peut gagner cette course et tous les autres threads reçoivent la valeur initialisée par le thread qui a réussi. Si une exception est levée sur un thread pendant l'initialisation, ce thread ne reçoit pas la valeur définie par le thread qui a réussi. Les exceptions ne sont pas mises en cache, donc une tentative ultérieure d'accéder à la propriété Value peut aboutir à une initialisation réussie. Cela diffère de la façon dont les exceptions sont traitées dans d'autres modes, ce qui est décrit dans la section suivante. Pour plus d'informations, consultez l'énumération LazyThreadSafetyMode.

Exceptions des objets tardifs

Comme indiqué précédemment, un objet Lazy<T> retourne toujours le même objet (ou valeur) avec lequel il a été initialisé, et, par conséquent, la propriété Value est en lecture seule. Si vous activez la mise en cache d'exceptions, cette immuabilité s'étend également au comportement des exceptions. Si un objet initialisé de façon tardive a la mise en cache d'exceptions activée et lève une exception à partir de sa méthode d'initialisation lorsque la propriété Value fait l'objet d'un premier accès, cette même exception est levée à chaque tentative suivante d'accès à la propriété Value. En d'autres termes, le constructeur du type inclus dans un wrapper n'est jamais rappelé, même dans des scénarios multithreads. Par conséquent, l'objet Lazy<T> ne peut pas lever une exception lors d'un accès et retourner une valeur lors d'un accès suivant.

La mise en cache d'exceptions est activée lorsque vous utilisez un constructeur System.Lazy<T> qui accepte une méthode d'initialisation (paramètre valueFactory) ; par exemple, elle est activée lorsque vous utilisez le constructeur Lazy(T)(Func(T)). Si le constructeur prend également une valeur LazyThreadSafetyMode (paramètre mode), spécifiez LazyThreadSafetyMode.None ou LazyThreadSafetyMode.ExecutionAndPublication. La spécification d'une méthode d'initialisation active la mise en cache d'exceptions pour ces deux modes. La méthode d'initialisation peut être très simple. Par exemple, elle peut appeler le constructeur par défaut pour T : new Lazy<Contents>(() => new Contents(), mode) dans C# ou New Lazy(Of Contents)(Function() New Contents()) dans Visual Basic. Si vous utilisez un constructeur System.Lazy<T> qui ne spécifie pas une méthode d'initialisation, les exceptions qui sont levées par le constructeur par défaut pour T ne sont pas mises en cache. Pour plus d'informations, consultez l'énumération LazyThreadSafetyMode.

RemarqueRemarque

Si vous créez un objet Lazy<T> avec le paramètre de constructeur isThreadSafe défini sur false ou le paramètre de constructeur mode défini sur LazyThreadSafetyMode.None, vous devez accéder à l'objet Lazy<T> à partir d'un thread unique ou fournir votre propre synchronisation.Cela s'applique à tous les aspects de l'objet, y compris la mise en cache d'exceptions.

Comme indiqué dans la section précédente, les objets Lazy<T> créés en spécifiant LazyThreadSafetyMode.PublicationOnly traitent les exceptions différemment. Avec PublicationOnly, plusieurs threads peuvent entrer en compétition pour initialiser l'instance Lazy<T>. Dans ce cas, les exceptions ne sont pas mises en cache et les tentatives d'accéder à la propriété Value peuvent continuer jusqu'à ce que l'initialisation soit réussie.

Le tableau suivant résume la façon dont les constructeurs Lazy<T> contrôlent la mise en cache d'exceptions.

Constructeur

Mode de sécurité des threads

Utilise la méthode d'initialisation

Les exceptions sont mises en cache

Lazy(T)()

(ExecutionAndPublication)

Non

Non

Lazy(T)(Func(T))

(ExecutionAndPublication)

Oui

Oui

Lazy(T)(Boolean)

True (ExecutionAndPublication) ou false (None)

Non

Non

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

True (ExecutionAndPublication) ou false (None)

Oui

Oui

Lazy(T)(LazyThreadSafetyMode)

Spécifié par l'utilisateur

Non

Non

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

Spécifié par l'utilisateur

Oui

Non si l'utilisateur spécifie PublicationOnly ; sinon, oui.

Implémentation d'une propriété initialisée tardivement

Pour implémenter une propriété publique à l'aide de l'initialisation tardive, définissez le champ de stockage de la propriété en tant que Lazy<T> et retournez la propriété Value de l'accesseur get de la propriété.

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 propriété Value est en lecture seule ; par conséquent, la propriété qui l'expose n'a aucun accesseur set. Si vous avez besoin d'une propriété en lecture/écriture stockée par un objet Lazy<T>, l'accesseur set doit créer un objet Lazy<T> et l'assigner au magasin de stockage. L'accesseur set doit créer une expression lambda qui retourne la nouvelle valeur de propriété passée à l'accesseur set et passe cette expression lambda au constructeur pour le nouvel objet Lazy<T>. L'accès suivant de la propriété Value provoque l'initialisation du nouveau Lazy<T> et sa propriété Value retourne par la suite la nouvelle valeur assignée à la propriété. La raison de cette disposition complexe est de conserver les protections de multithreading intégrées à Lazy<T>. Sinon, les accesseurs de propriété devront mettre en cache la première valeur retournée par la propriété Value et uniquement modifier la valeur mise en cache et vous devrez écrire votre propre code thread-safe pour ce faire. Les performances risquent de ne pas être acceptables à cause des initialisations supplémentaires requises par une propriété en lecture/écriture stockée par un objet Lazy<T>. En outre, selon le scénario spécifique, la coordination supplémentaire peut être requise pour éviter des conditions de concurrence entre accesseurs Set et accesseurs Get.

Initialisation tardive de thread local

Dans certains scénarios multithreads, vous pouvez souhaiter donner à chaque thread ses propres données privées. Ces données sont appelées données locales de thread. Dans le .NET Framework version 3.5 et versions antérieures, vous pouviez appliquer l'attribut ThreadStatic à une variable statique pour en faire un thread local. Cependant, l'utilisation de l'attribut ThreadStatic peut générer de petites erreurs. Par exemple, même des instructions d'initialisation de base entraîneront l'initialisation de la variable uniquement sur le premier thread qui y accède, comme indiqué dans l'exemple suivant.

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

Sur tous les autres threads, la variable sera initialisée à l'aide de sa valeur par défaut (zéro). Dans le .NET Framework version 4, vous pouvez utiliser le type System.Threading.ThreadLocal<T> pour créer une variable de thread locale basée sur une instance, initialisée sur tous les threads par le délégué Action<T> que vous fournissez. Dans l'exemple suivant, tous les threads qui accèdent à counter voient sa valeur de départ définie sur 1.

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

ThreadLocal<T> inclut dans un wrapper son objet à peu près de la même façon que Lazy<T>, avec ces différences essentielles :

  • Chaque thread initialise la variable de thread locale à l'aide de ses propres données privées qui ne sont pas accessibles à partir des autres threads.

  • La propriété ThreadLocal<T>.Value est en lecture-écriture et peut être modifiée aussi souvent que nécessaire. Ceci peut affecter la propagation des exceptions. Par exemple, une opération get peut lever une exception, tandis que la suivante peut initialiser la valeur avec succès.

  • Si aucun délégué d'initialisation n'est fourni, ThreadLocal<T> initialisera son type inclus dans un wrapper à l'aide de la valeur par défaut du type. À cet égard, ThreadLocal<T> est cohérent avec l'attribut ThreadStaticAttribute.

L'exemple suivant montre que chaque thread qui accède à l'instance ThreadLocal<int> obtient sa propre copie unique des données.

' 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 de thread locales dans Parallel.For et ForEach

Lorsque vous utilisez la méthode Parallel.For ou Parallel.ForEach pour itérer au sein des sources de données en parallèle, vous pouvez utiliser les surcharges avec prise en charge intégrée pour les données de thread locales. Dans ces méthodes, l'emplacement des threads est obtenu à l'aide de délégués locaux qui créent les données, y accèdent et les nettoient. Pour plus d'informations, consultez Comment : écrire une boucle Parallel.For comprenant des variables locales de thread et Comment : écrire une boucle Parallel.ForEach comprenant des variables de thread local.

Utilisation de l'initialisation tardive pour des scénarios de faible charge

Dans les scénarios où vous devez initialiser tardivement un grand nombre d'objets, vous pouvez décider que l'inclusion dans un wrapper de chaque objet dans un Lazy<T> requiert trop de mémoire ou trop de ressources de calcul. Vous pouvez également posséder des exigences strictes à propos de la façon dont l'initialisation tardive est exposée. Dans les tels cas, vous pouvez utiliser les méthodes staticShared en Visual Basic) de la classe System.Threading.LazyInitializer pour initialiser de façon tardive chaque objet sans l'inclure dans un wrapper dans une instance de Lazy<T>.

Dans l'exemple suivant, supposez que, au lieu d'inclure dans un wrapper un objet Orders entier dans un objet Lazy<T>, vous avez des objets Order individuels initialisés tardivement uniquement s'ils sont requis.

' 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);
            });
    }
}

Dans cet exemple, remarquez que la procédure d'initialisation est appelée sur chaque itération de la boucle. Dans les scénarios multithreads, le premier thread à appeler la procédure d'initialisation est celui dont la valeur est vue par tous les threads. Les threads suivants appellent également la procédure d'initialisation, mais leurs résultats ne sont pas utilisés. Si ce genre de condition de concurrence potentielle n'est pas acceptable, utilisez la surcharge de LazyInitializer.EnsureInitialized qui utilise un argument booléen et un objet de synchronisation.

Voir aussi

Tâches

Comment : effectuer une initialisation tardive d'objets

Concepts

Threads et threading

Bibliothèque parallèle de tâches

Autres ressources

Éléments fondamentaux du threading managé

Historique des modifications

Date

Historique

Motif

Mars 2011

Les informations sur la mise en cache d'exceptions ont été corrigées.

Résolution des bogues de contenu.

Avril 2011

D'autres révisions ont été apportées aux informations sur la mise en cache d'exceptions.

Résolution des bogues de contenu.

Avril 2011

Correction : L'appel de Lazy<T>.ToString ne provoque pas l'initialisation.

Résolution des bogues de contenu.