Compartilhar via


Lazy<T> Classe

Definição

Fornece suporte para inicialização lenta.

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)

Parâmetros de tipo

T

O tipo de objeto que está sendo inicializado lentamente.

Herança
Lazy<T>
Derivado
Atributos

Exemplos

O exemplo a seguir demonstra o uso da classe Lazy<T> para fornecer inicialização lenta com acesso de vários threads.

Nota

O exemplo usa o construtor Lazy<T>(Func<T>). Ele também demonstra o uso do construtor Lazy<T>(Func<T>, Boolean) (especificando true para isThreadSafe) e do construtor Lazy<T>(Func<T>, LazyThreadSafetyMode) (especificando LazyThreadSafetyMode.ExecutionAndPublication para mode). Para alternar para um construtor diferente, basta alterar quais construtores são comentados.

Para obter um exemplo que demonstra o cache de exceções usando os mesmos construtores, consulte o construtor Lazy<T>(Func<T>).

O exemplo define uma classe LargeObject que será inicializada de forma preguiçosa por um dos vários threads. As quatro seções principais do código ilustram a criação do inicializador, o método de fábrica, a inicialização real e o construtor da classe LargeObject, que exibe uma mensagem quando o objeto é criado. No início do método Main, o exemplo cria o inicializador lento de thread-safe para 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)

O método de fábrica mostra a criação do objeto, com um espaço reservado para inicialização adicional:

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

Observe que as duas primeiras seções de código podem ser combinadas usando uma função lambda, conforme mostrado aqui:

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)

O exemplo pausa, para indicar que um período indeterminado pode decorrer antes da inicialização lenta ocorrer. Quando você pressiona a tecla Enter, o exemplo cria e inicia três threads. O método ThreadProc usado pelos três threads chama a propriedade Value. Na primeira vez que isso acontecer, a instância LargeObject será criada:

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

O construtor da classe LargeObject, que inclui a última seção de chave do código, exibe uma mensagem e registra a identidade do thread de inicialização. A saída do programa é exibida no final da listagem de código completa.

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

Para simplificar, este exemplo usa uma instância global de Lazy<T>e todos os métodos são static (Shared no Visual Basic). Esses não são requisitos para o uso da inicialização lenta.

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
'

Comentários

Use a inicialização lenta para adiar a criação de um objeto grande ou com uso intensivo de recursos ou a execução de uma tarefa com uso intensivo de recursos, especialmente quando essa criação ou execução pode não ocorrer durante o tempo de vida do programa.

Para se preparar para a inicialização lenta, crie uma instância de Lazy<T>. O argumento de tipo do objeto Lazy<T> que você cria especifica o tipo do objeto que você deseja inicializar preguiçosamente. O construtor que você usa para criar o objeto Lazy<T> determina as características da inicialização. A inicialização lenta ocorre na primeira vez em que a propriedade Lazy<T>.Value é acessada.

Na maioria dos casos, escolher um construtor depende de suas respostas para duas perguntas:

  • O objeto inicializado preguiçosamente será acessado de mais de um thread? Nesse caso, o objeto Lazy<T> poderá criá-lo em qualquer thread. Você pode usar um dos construtores simples cujo comportamento padrão é criar um objeto Lazy<T> thread-safe, de modo que apenas uma instância do objeto instanciado preguiçosamente seja criada, independentemente de quantos threads tentem acessá-lo. Para criar um objeto Lazy<T> que não seja thread-safe, você deve usar um construtor que permita que você não especifique nenhuma segurança de thread.

    Cuidado

    Tornar o thread de objeto Lazy<T> seguro não protege o objeto inicializado preguiçosamente. Se vários threads puderem acessar o objeto inicializado preguiçosamente, você deverá tornar suas propriedades e métodos seguros para acesso multithread.

  • A inicialização lenta requer muito código ou o objeto inicializado preguiçosamente tem um construtor sem parâmetros que faz tudo o que você precisa e não gera exceções? Se você precisar escrever o código de inicialização ou se as exceções precisarem ser tratadas, use um dos construtores que usa um método de fábrica. Escreva o código de inicialização no método de fábrica.

A tabela a seguir mostra qual construtor escolher, com base nesses dois fatores:

O objeto será acessado por Se nenhum código de inicialização for necessário (construtor sem parâmetros), use Se o código de inicialização for necessário, use
Vários threads Lazy<T>() Lazy<T>(Func<T>)
Um thread Lazy<T>(Boolean) com isThreadSafe definido como false. Lazy<T>(Func<T>, Boolean) com isThreadSafe definido como false.

Você pode usar uma expressão lambda para especificar o método de fábrica. Isso mantém todo o código de inicialização em um só lugar. A expressão lambda captura o contexto, incluindo os argumentos que você passa para o construtor do objeto inicializado preguiçosamente inicializado.

cache de exceção Quando você usa métodos de fábrica, as exceções são armazenadas em cache. Ou seja, se o método de fábrica gerar uma exceção na primeira vez que um thread tentar acessar a propriedade Value do objeto Lazy<T>, a mesma exceção será gerada em todas as tentativas subsequentes. Isso garante que cada chamada à propriedade Value produza o mesmo resultado e evita erros sutis que possam surgir se threads diferentes obtiverem resultados diferentes. O Lazy<T> representa uma T real que, de outra forma, teria sido inicializada em algum momento anterior, geralmente durante a inicialização. Uma falha nesse ponto anterior geralmente é fatal. Se houver um potencial para uma falha recuperável, recomendamos que você crie a lógica de repetição na rotina de inicialização (nesse caso, o método de fábrica), assim como faria se não estivesse usando a inicialização lenta.

Alternativa ao bloqueio de Em determinadas situações, convém evitar a sobrecarga do comportamento de bloqueio padrão do objeto Lazy<T>. Em situações raras, pode haver um potencial para deadlocks. Nesses casos, você pode usar o construtor Lazy<T>(LazyThreadSafetyMode) ou Lazy<T>(Func<T>, LazyThreadSafetyMode) e especificar LazyThreadSafetyMode.PublicationOnly. Isso permite que o objeto Lazy<T> crie uma cópia do objeto inicializado preguiçosamente em cada um dos vários threads se os threads chamarem a propriedade Value simultaneamente. O objeto Lazy<T> garante que todos os threads usem a mesma instância do objeto inicializado preguiçosamente e descarta as instâncias que não são usadas. Portanto, o custo de reduzir a sobrecarga de bloqueio é que seu programa pode, às vezes, criar e descartar cópias extras de um objeto caro. Na maioria dos casos, isso é improvável. Os exemplos dos construtores Lazy<T>(LazyThreadSafetyMode) e Lazy<T>(Func<T>, LazyThreadSafetyMode) demonstram esse comportamento.

Importante

Quando você especifica LazyThreadSafetyMode.PublicationOnly, as exceções nunca são armazenadas em cache, mesmo se você especificar um método de fábrica.

Construtores equivalentes Além de habilitar o uso de LazyThreadSafetyMode.PublicationOnly, os construtores Lazy<T>(LazyThreadSafetyMode) e Lazy<T>(Func<T>, LazyThreadSafetyMode) podem duplicar a funcionalidade dos outros construtores. A tabela a seguir mostra os valores de parâmetro que produzem comportamento equivalente.

Para criar um objeto Lazy<T> que é Para construtores que têm um parâmetro LazyThreadSafetyModemode, defina mode como Para construtores que têm um parâmetro booliano isThreadSafe, defina isThreadSafe como Para construtores sem parâmetros de segurança de thread
Totalmente thread seguro; usa o bloqueio para garantir que apenas um thread inicialize o valor. ExecutionAndPublication true Todos esses construtores são totalmente thread-safe.
Não thread-safe. None false Não aplicável.
Totalmente thread seguro; threads correm para inicializar o valor. PublicationOnly Não aplicável. Não aplicável.

Outros recursos Para obter informações sobre o uso de Lazy<T> com campos estáticos de thread ou como repositório de backup para propriedades, consulte Inicialização Lenta.

Construtores

Lazy<T>()

Inicializa uma nova instância da classe Lazy<T>. Quando ocorre uma inicialização lenta, o construtor sem parâmetros do tipo de destino é usado.

Lazy<T>(Boolean)

Inicializa uma nova instância da classe Lazy<T>. Quando ocorre uma inicialização lenta, o construtor sem parâmetros do tipo de destino e o modo de inicialização especificado são usados.

Lazy<T>(Func<T>)

Inicializa uma nova instância da classe Lazy<T>. Quando ocorre uma inicialização lenta, a função de inicialização especificada é usada.

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

Inicializa uma nova instância da classe Lazy<T>. Quando ocorre uma inicialização lenta, a função de inicialização especificada e o modo de inicialização são usados.

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

Inicializa uma nova instância da classe Lazy<T> que usa a função de inicialização especificada e o modo de segurança de thread.

Lazy<T>(LazyThreadSafetyMode)

Inicializa uma nova instância da classe Lazy<T> que usa o construtor sem parâmetros de T e o modo de segurança de thread especificado.

Lazy<T>(T)

Inicializa uma nova instância da classe Lazy<T> que usa um valor especificado pré-inicializado.

Propriedades

IsValueCreated

Obtém um valor que indica se um valor foi criado para essa instância Lazy<T>.

Value

Obtém o valor inicializado preguiçosamente da instância de Lazy<T> atual.

Métodos

Equals(Object)

Determina se o objeto especificado é igual ao objeto atual.

(Herdado de Object)
GetHashCode()

Serve como a função de hash padrão.

(Herdado de Object)
GetType()

Obtém o Type da instância atual.

(Herdado de Object)
MemberwiseClone()

Cria uma cópia superficial do Objectatual.

(Herdado de Object)
ToString()

Cria e retorna uma representação de cadeia de caracteres da propriedade Value para essa instância.

Aplica-se a

Acesso thread-safe

Por padrão, todos os membros públicos e protegidos da classe Lazy<T> são thread-safe e podem ser usados simultaneamente de vários threads. Essas garantias de segurança de thread podem ser removidas opcionalmente e por instância, usando parâmetros para os construtores do tipo.

Confira também