Condividi tramite


Lazy<T> Classe

Definizione

Fornisce supporto per l'inizializzazione differita.

generic <typename T>
public ref class Lazy
public class Lazy<T>
[System.Runtime.InteropServices.ComVisible(false)]
[System.Serializable]
public class Lazy<T>
type Lazy<'T> = class
[<System.Runtime.InteropServices.ComVisible(false)>]
[<System.Serializable>]
type Lazy<'T> = class
Public Class Lazy(Of T)

Parametri di tipo

T

Tipo di oggetto inizializzato in modo differire.

Ereditarietà
Lazy<T>
Derivato
Attributi

Esempio

Nell'esempio seguente viene illustrato l'uso della classe Lazy<T> per fornire l'inizializzazione differita con accesso da più thread.

Nota

Nell'esempio viene utilizzato il costruttore Lazy<T>(Func<T>). Viene inoltre illustrato l'uso del costruttore Lazy<T>(Func<T>, Boolean) (specificando true per isThreadSafe) e del costruttore Lazy<T>(Func<T>, LazyThreadSafetyMode) (specificando LazyThreadSafetyMode.ExecutionAndPublication per mode). Per passare a un costruttore diverso, è sufficiente modificare i costruttori che vengono commentati.

Per un esempio che illustra la memorizzazione nella cache delle eccezioni usando gli stessi costruttori, vedere il costruttore Lazy<T>(Func<T>).

L'esempio definisce una classe LargeObject che verrà inizializzata in modo differire da uno dei diversi thread. Le quattro sezioni chiave del codice illustrano la creazione dell'inizializzatore, il metodo factory, l'inizializzazione effettiva e il costruttore della classe LargeObject, che visualizza un messaggio quando viene creato l'oggetto. All'inizio del metodo Main, nell'esempio viene creato l'inizializzatore lazy thread-safe per LargeObject:

lazyLargeObject = new Lazy<LargeObject>(InitLargeObject);

// The following lines show how to use other constructors to achieve exactly the
// same result as the previous line:
//lazyLargeObject = new Lazy<LargeObject>(InitLargeObject, true);
//lazyLargeObject = new Lazy<LargeObject>(InitLargeObject,
//                               LazyThreadSafetyMode.ExecutionAndPublication);
let lazyLargeObject = Lazy<LargeObject> initLargeObject

// The following lines show how to use other constructors to achieve exactly the
// same result as the previous line:
//     let lazyLargeObject = Lazy<LargeObject>(initLargeObject, true)
//     let lazyLargeObject = Lazy<LargeObject>(initLargeObject,
//                               LazyThreadSafetyMode.ExecutionAndPublication)
lazyLargeObject = New Lazy(Of LargeObject)(AddressOf InitLargeObject)

' The following lines show how to use other constructors to achieve exactly the
' same result as the previous line: 
'lazyLargeObject = New Lazy(Of LargeObject)(AddressOf InitLargeObject, True)
'lazyLargeObject = New Lazy(Of LargeObject)(AddressOf InitLargeObject, _
'                               LazyThreadSafetyMode.ExecutionAndPublication)

Il metodo factory mostra la creazione dell'oggetto, con un segnaposto per un'ulteriore inizializzazione:

static LargeObject InitLargeObject()
{
    LargeObject large = new LargeObject(Thread.CurrentThread.ManagedThreadId);
    // Perform additional initialization here.
    return large;
}
let initLargeObject () =
    let large = LargeObject Thread.CurrentThread.ManagedThreadId
    // Perform additional initialization here.
    large
Private Shared Function InitLargeObject() As LargeObject
    Dim large As New LargeObject(Thread.CurrentThread.ManagedThreadId)
    ' Perform additional initialization here.
    Return large
End Function

Si noti che le prime due sezioni di codice possono essere combinate usando una funzione lambda, come illustrato di seguito:

lazyLargeObject = new Lazy<LargeObject>(() =>
{
    LargeObject large = new LargeObject(Thread.CurrentThread.ManagedThreadId);
    // Perform additional initialization here.
    return large;
});
let lazyLargeObject = Lazy<LargeObject>(fun () ->
    let large = LargeObject Thread.CurrentThread.ManagedThreadId
    // Perform additional initialization here.
    large)
lazyLargeObject = New Lazy(Of LargeObject)(Function () 
    Dim large As New LargeObject(Thread.CurrentThread.ManagedThreadId) 
    ' Perform additional initialization here.
    Return large
End Function)

L'esempio sospende, per indicare che un periodo indeterminato può trascorrere prima che si verifichi l'inizializzazione differita. Quando si preme il tasto INVIO, l'esempio crea e avvia tre thread. Il metodo ThreadProc utilizzato da tutti e tre i thread chiama la proprietà Value. La prima volta che si verifica questa situazione, viene creata l'istanza di LargeObject:

LargeObject large = lazyLargeObject.Value;

// IMPORTANT: Lazy initialization is thread-safe, but it doesn't protect the
//            object after creation. You must lock the object before accessing it,
//            unless the type is thread safe. (LargeObject is not thread safe.)
lock(large)
{
    large.Data[0] = Thread.CurrentThread.ManagedThreadId;
    Console.WriteLine("Initialized by thread {0}; last used by thread {1}.",
        large.InitializedBy, large.Data[0]);
}
let large = lazyLargeObject.Value

// IMPORTANT: Lazy initialization is thread-safe, but it doesn't protect the
//            object after creation. You must lock the object before accessing it,
//            unless the type is thread safe. (LargeObject is not thread safe.)
lock large (fun () ->
    large.Data[0] <- Thread.CurrentThread.ManagedThreadId
    printfn $"Initialized by thread {large.InitializedBy} last used by thread {large.Data[0]}.")
Dim large As LargeObject = lazyLargeObject.Value

' IMPORTANT: Lazy initialization is thread-safe, but it doesn't protect the  
'            object after creation. You must lock the object before accessing it,
'            unless the type is thread safe. (LargeObject is not thread safe.)
SyncLock large
    large.Data(0) = Thread.CurrentThread.ManagedThreadId
    Console.WriteLine("Initialized by thread {0}; last used by thread {1}.", _
        large.InitializedBy, large.Data(0))
End SyncLock

Il costruttore della classe LargeObject, che include l'ultima sezione chiave del codice, visualizza un messaggio e registra l'identità del thread di inizializzazione. L'output del programma viene visualizzato alla fine dell'elenco di codice completo.

int initBy = 0;
public LargeObject(int initializedBy)
{
    initBy = initializedBy;
    Console.WriteLine("LargeObject was created on thread id {0}.", initBy);
}
type LargeObject(initBy) =
    do 
        printfn $"LargeObject was created on thread id %i{initBy}."
Private initBy As Integer = 0
Public Sub New(ByVal initializedBy As Integer)
    initBy = initializedBy
    Console.WriteLine("LargeObject was created on thread id {0}.", initBy)
End Sub

Nota

Per semplicità, questo esempio usa un'istanza globale di Lazy<T>e tutti i metodi sono static (Shared in Visual Basic). Questi non sono requisiti per l'uso dell'inizializzazione differita.

using System;
using System.Threading;

class Program
{
    static Lazy<LargeObject> lazyLargeObject = null;

    static LargeObject InitLargeObject()
    {
        LargeObject large = new LargeObject(Thread.CurrentThread.ManagedThreadId);
        // Perform additional initialization here.
        return large;
    }

    static void Main()
    {
        // The lazy initializer is created here. LargeObject is not created until the
        // ThreadProc method executes.
        lazyLargeObject = new Lazy<LargeObject>(InitLargeObject);

        // The following lines show how to use other constructors to achieve exactly the
        // same result as the previous line:
        //lazyLargeObject = new Lazy<LargeObject>(InitLargeObject, true);
        //lazyLargeObject = new Lazy<LargeObject>(InitLargeObject,
        //                               LazyThreadSafetyMode.ExecutionAndPublication);

        Console.WriteLine(
            "\r\nLargeObject is not created until you access the Value property of the lazy" +
            "\r\ninitializer. Press Enter to create LargeObject.");
        Console.ReadLine();

        // Create and start 3 threads, each of which uses LargeObject.
        Thread[] threads = new Thread[3];
        for (int i = 0; i < 3; i++)
        {
            threads[i] = new Thread(ThreadProc);
            threads[i].Start();
        }

        // Wait for all 3 threads to finish.
        foreach (Thread t in threads)
        {
            t.Join();
        }

        Console.WriteLine("\r\nPress Enter to end the program");
        Console.ReadLine();
    }

    static void ThreadProc(object state)
    {
        LargeObject large = lazyLargeObject.Value;

        // IMPORTANT: Lazy initialization is thread-safe, but it doesn't protect the
        //            object after creation. You must lock the object before accessing it,
        //            unless the type is thread safe. (LargeObject is not thread safe.)
        lock(large)
        {
            large.Data[0] = Thread.CurrentThread.ManagedThreadId;
            Console.WriteLine("Initialized by thread {0}; last used by thread {1}.",
                large.InitializedBy, large.Data[0]);
        }
    }
}

class LargeObject
{
    public int InitializedBy { get { return initBy; } }

    int initBy = 0;
    public LargeObject(int initializedBy)
    {
        initBy = initializedBy;
        Console.WriteLine("LargeObject was created on thread id {0}.", initBy);
    }

    public long[] Data = new long[100000000];
}

/* This example produces output similar to the following:

LargeObject is not created until you access the Value property of the lazy
initializer. Press Enter to create LargeObject.

LargeObject was created on thread id 3.
Initialized by thread 3; last used by thread 3.
Initialized by thread 3; last used by thread 4.
Initialized by thread 3; last used by thread 5.

Press Enter to end the program
 */
open System
open System.Threading

type LargeObject(initBy) =
    do 
        printfn $"LargeObject was created on thread id %i{initBy}."
    member _.InitializedBy = initBy
    member val Data = Array.zeroCreate<int64> 100000000

let initLargeObject () =
    let large = LargeObject Thread.CurrentThread.ManagedThreadId
    // Perform additional initialization here.
    large

// The lazy initializer is created here. LargeObject is not created until the
// ThreadProc method executes.
let lazyLargeObject = Lazy<LargeObject> initLargeObject

// The following lines show how to use other constructors to achieve exactly the
// same result as the previous line:
//     let lazyLargeObject = Lazy<LargeObject>(initLargeObject, true)
//     let lazyLargeObject = Lazy<LargeObject>(initLargeObject,
//                               LazyThreadSafetyMode.ExecutionAndPublication)

let threadProc (state: obj) =
    let large = lazyLargeObject.Value

    // IMPORTANT: Lazy initialization is thread-safe, but it doesn't protect the
    //            object after creation. You must lock the object before accessing it,
    //            unless the type is thread safe. (LargeObject is not thread safe.)
    lock large (fun () ->
        large.Data[0] <- Thread.CurrentThread.ManagedThreadId
        printfn $"Initialized by thread {large.InitializedBy} last used by thread {large.Data[0]}.")

printfn """
LargeObject is not created until you access the Value property of the lazy
initializer. Press Enter to create LargeObject."""
stdin.ReadLine() |> ignore

// Create and start 3 threads, each of which uses LargeObject.

let threads = Array.zeroCreate 3
for i = 0 to 2 do
    threads[i] <- Thread(ParameterizedThreadStart threadProc)
    threads[i].Start()

// Wait for all 3 threads to finish.
for t in threads do
    t.Join()

printfn "\nPress Enter to end the program"
stdin.ReadLine() |> ignore

// This example produces output similar to the following:
//     LargeObject is not created until you access the Value property of the lazy
//     initializer. Press Enter to create LargeObject.
//     
//     LargeObject was created on thread id 3.
//     Initialized by thread 3 last used by thread 3.
//     Initialized by thread 3 last used by thread 4.
//     Initialized by thread 3 last used by thread 5.
//     
//     Press Enter to end the program
Imports System.Threading

Friend Class Program
    Private Shared lazyLargeObject As Lazy(Of LargeObject) = Nothing

    Private Shared Function InitLargeObject() As LargeObject
        Dim large As New LargeObject(Thread.CurrentThread.ManagedThreadId)
        ' Perform additional initialization here.
        Return large
    End Function


    Shared Sub Main()
        ' The lazy initializer is created here. LargeObject is not created until the 
        ' ThreadProc method executes.
        lazyLargeObject = New Lazy(Of LargeObject)(AddressOf InitLargeObject)

        ' The following lines show how to use other constructors to achieve exactly the
        ' same result as the previous line: 
        'lazyLargeObject = New Lazy(Of LargeObject)(AddressOf InitLargeObject, True)
        'lazyLargeObject = New Lazy(Of LargeObject)(AddressOf InitLargeObject, _
        '                               LazyThreadSafetyMode.ExecutionAndPublication)


        Console.WriteLine(vbCrLf & _
            "LargeObject is not created until you access the Value property of the lazy" _
            & vbCrLf & "initializer. Press Enter to create LargeObject.")
        Console.ReadLine()

        ' Create and start 3 threads, each of which uses LargeObject.
        Dim threads(2) As Thread
        For i As Integer = 0 To 2
            threads(i) = New Thread(AddressOf ThreadProc)
            threads(i).Start()
        Next i

        ' Wait for all 3 threads to finish. 
        For Each t As Thread In threads
            t.Join()
        Next t

        Console.WriteLine(vbCrLf & "Press Enter to end the program")
        Console.ReadLine()
    End Sub


    Private Shared Sub ThreadProc(ByVal state As Object)
        Dim large As LargeObject = lazyLargeObject.Value

        ' IMPORTANT: Lazy initialization is thread-safe, but it doesn't protect the  
        '            object after creation. You must lock the object before accessing it,
        '            unless the type is thread safe. (LargeObject is not thread safe.)
        SyncLock large
            large.Data(0) = Thread.CurrentThread.ManagedThreadId
            Console.WriteLine("Initialized by thread {0}; last used by thread {1}.", _
                large.InitializedBy, large.Data(0))
        End SyncLock
    End Sub
End Class

Friend Class LargeObject
    Public ReadOnly Property InitializedBy() As Integer
        Get
            Return initBy
        End Get
    End Property

    Private initBy As Integer = 0
    Public Sub New(ByVal initializedBy As Integer)
        initBy = initializedBy
        Console.WriteLine("LargeObject was created on thread id {0}.", initBy)
    End Sub

    Public Data(99999999) As Long
End Class

' This example produces output similar to the following:
'
'LargeObject is not created until you access the Value property of the lazy
'initializer. Press Enter to create LargeObject.
'
'LargeObject was created on thread id 3.
'Initialized by thread 3; last used by thread 3.
'Initialized by thread 3; last used by thread 5.
'Initialized by thread 3; last used by thread 4.
'
'Press Enter to end the program
'

Commenti

Usare l'inizializzazione differita per rinviare la creazione di un oggetto a elevato utilizzo di risorse o l'esecuzione di un'attività a elevato utilizzo di risorse, in particolare quando tale creazione o esecuzione potrebbe non verificarsi durante la durata del programma.

Per preparare l'inizializzazione differita, creare un'istanza di Lazy<T>. L'argomento type dell'oggetto Lazy<T> creato specifica il tipo dell'oggetto che si desidera inizializzare in modo differire. Il costruttore utilizzato per creare l'oggetto Lazy<T> determina le caratteristiche dell'inizializzazione. L'inizializzazione differita si verifica la prima volta che si accede alla proprietà Lazy<T>.Value.

Nella maggior parte dei casi, la scelta di un costruttore dipende dalle risposte a due domande:

  • Sarà possibile accedere all'oggetto inizializzato in modo differire da più thread? In tal caso, l'oggetto Lazy<T> potrebbe crearlo in qualsiasi thread. È possibile usare uno dei semplici costruttori il cui comportamento predefinito consiste nel creare un oggetto Lazy<T> thread-safe, in modo che venga creata solo un'istanza dell'oggetto di cui viene creata un'istanza differita indipendentemente dal numero di thread che tentano di accedervi. Per creare un oggetto Lazy<T> non thread-safe, è necessario utilizzare un costruttore che consente di specificare nessun thread safety.

    Cautela

    Rendere thread-safe dell'oggetto Lazy<T> non protegge l'oggetto inizializzato in modo differito. Se più thread possono accedere all'oggetto inizializzato lazialmente, è necessario rendere le relative proprietà e metodi sicuri per l'accesso multithreading.

  • L'inizializzazione differita richiede un sacco di codice o l'oggetto inizializzato in modo differita ha un costruttore senza parametri che esegue tutte le operazioni necessarie e non genera eccezioni? Se è necessario scrivere codice di inizializzazione o se è necessario gestire le eccezioni, usare uno dei costruttori che accetta un metodo factory. Scrivere il codice di inizializzazione nel metodo factory.

La tabella seguente illustra il costruttore da scegliere, in base a questi due fattori:

L'oggetto sarà accessibile da Se non è necessario alcun codice di inizializzazione (costruttore senza parametri), usare Se è necessario il codice di inizializzazione, usare
Più thread Lazy<T>() Lazy<T>(Func<T>)
Un thread Lazy<T>(Boolean) con isThreadSafe impostato su false. Lazy<T>(Func<T>, Boolean) con isThreadSafe impostato su false.

È possibile usare un'espressione lambda per specificare il metodo factory. In questo modo tutto il codice di inizializzazione viene mantenuto in un'unica posizione. L'espressione lambda acquisisce il contesto, inclusi gli argomenti passati al costruttore dell'oggetto inizializzato in modo differinte.

memorizzazione nella cache delle eccezioni Quando si usano i metodi factory, le eccezioni vengono memorizzate nella cache. Ovvero, se il metodo factory genera un'eccezione la prima volta che un thread tenta di accedere alla proprietà Value dell'oggetto Lazy<T>, viene generata la stessa eccezione in ogni tentativo successivo. In questo modo ogni chiamata alla proprietà Value produce lo stesso risultato ed evita errori sottili che possono verificarsi se diversi thread ottengono risultati diversi. Il Lazy<T> si trova per un T effettivo che altrimenti sarebbe stato inizializzato in un momento precedente, in genere durante l'avvio. Un errore in quel punto precedente è in genere irreversibile. Se si verifica un potenziale errore ripristinabile, è consigliabile compilare la logica di ripetizione dei tentativi nella routine di inizializzazione (in questo caso, il metodo factory), proprio come se non si usasse l'inizializzazione differita.

Alternativa al blocco In determinate situazioni, è possibile evitare il sovraccarico del comportamento di blocco predefinito dell'oggetto Lazy<T>. In rari casi, potrebbe esserci un potenziale di deadlock. In questi casi, è possibile usare il costruttore Lazy<T>(LazyThreadSafetyMode) o Lazy<T>(Func<T>, LazyThreadSafetyMode) e specificare LazyThreadSafetyMode.PublicationOnly. Ciò consente all'oggetto Lazy<T> di creare una copia dell'oggetto inizializzato in modo differinte su ognuno di più thread se i thread chiamano simultaneamente la proprietà Value. L'oggetto Lazy<T> garantisce che tutti i thread usino la stessa istanza dell'oggetto inizializzato lazialmente e elimini le istanze non usate. Pertanto, il costo di ridurre il sovraccarico di blocco è che il programma potrebbe talvolta creare e rimuovere copie aggiuntive di un oggetto costoso. Nella maggior parte dei casi, questo è improbabile. Gli esempi per i costruttori Lazy<T>(LazyThreadSafetyMode) e Lazy<T>(Func<T>, LazyThreadSafetyMode) illustrano questo comportamento.

Importante

Quando si specifica LazyThreadSafetyMode.PublicationOnly, le eccezioni non vengono mai memorizzate nella cache, anche se si specifica un metodo factory.

costruttori equivalenti Oltre ad abilitare l'uso di LazyThreadSafetyMode.PublicationOnly, i costruttori Lazy<T>(LazyThreadSafetyMode) e Lazy<T>(Func<T>, LazyThreadSafetyMode) possono duplicare la funzionalità degli altri costruttori. Nella tabella seguente vengono illustrati i valori dei parametri che producono un comportamento equivalente.

Per creare un oggetto Lazy<T> che è Per i costruttori con un parametro LazyThreadSafetyModemode, impostare mode su Per i costruttori con un parametro isThreadSafe booleano, impostare isThreadSafe su Per i costruttori senza parametri di thread safety
Completamente thread-safe; usa il blocco per garantire che un solo thread inizializzi il valore. ExecutionAndPublication true Tutti questi costruttori sono completamente thread-safe.
Non thread-safe. None false Non applicabile.
Completamente thread-safe; i thread corre per inizializzare il valore. PublicationOnly Non applicabile. Non applicabile.

Altre funzionalità Per informazioni sull'uso di Lazy<T> con campi statici del thread o come archivio di backup per le proprietà, vedere Inizializzazione differita.

Costruttori

Lazy<T>()

Inizializza una nuova istanza della classe Lazy<T>. Quando si verifica l'inizializzazione differita, viene usato il costruttore senza parametri del tipo di destinazione.

Lazy<T>(Boolean)

Inizializza una nuova istanza della classe Lazy<T>. Quando si verifica l'inizializzazione differita, viene utilizzato il costruttore senza parametri del tipo di destinazione e la modalità di inizializzazione specificata.

Lazy<T>(Func<T>)

Inizializza una nuova istanza della classe Lazy<T>. Quando si verifica l'inizializzazione differita, viene utilizzata la funzione di inizializzazione specificata.

Lazy<T>(Func<T>, Boolean)

Inizializza una nuova istanza della classe Lazy<T>. Quando si verifica l'inizializzazione differita, vengono utilizzate la funzione di inizializzazione e la modalità di inizializzazione specificate.

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

Inizializza una nuova istanza della classe Lazy<T> che utilizza la funzione di inizializzazione specificata e la modalità thread-safety specificati.

Lazy<T>(LazyThreadSafetyMode)

Inizializza una nuova istanza della classe Lazy<T> che utilizza il costruttore senza parametri di T e la modalità thread-safety specificata.

Lazy<T>(T)

Inizializza una nuova istanza della classe Lazy<T> che utilizza un valore specificato preinitializzato.

Proprietà

IsValueCreated

Ottiene un valore che indica se è stato creato un valore per questa istanza di Lazy<T>.

Value

Ottiene il valore inizializzato differire dell'istanza di Lazy<T> corrente.

Metodi

Equals(Object)

Determina se l'oggetto specificato è uguale all'oggetto corrente.

(Ereditato da Object)
GetHashCode()

Funge da funzione hash predefinita.

(Ereditato da Object)
GetType()

Ottiene il Type dell'istanza corrente.

(Ereditato da Object)
MemberwiseClone()

Crea una copia superficiale del Objectcorrente.

(Ereditato da Object)
ToString()

Crea e restituisce una rappresentazione di stringa della proprietà Value per questa istanza.

Si applica a

Thread safety

Per impostazione predefinita, tutti i membri pubblici e protetti della classe Lazy<T> sono thread-safe e possono essere usati simultaneamente da più thread. Queste garanzie di thread-safety possono essere rimosse facoltativamente e per ogni istanza, usando parametri per i costruttori del tipo.

Vedi anche