Udostępnij za pośrednictwem


Lazy<T> Klasa

Definicja

Zapewnia obsługę inicjowania leniwego.

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)

Parametry typu

T

Typ obiektu, który jest inicjowany leniwie.

Dziedziczenie
Lazy<T>
Pochodne
Atrybuty

Przykłady

W poniższym przykładzie pokazano użycie klasy Lazy<T> w celu zapewnienia opóźnionego inicjowania z dostępem z wielu wątków.

Nuta

W przykładzie użyto konstruktora Lazy<T>(Func<T>). Demonstruje również użycie konstruktora Lazy<T>(Func<T>, Boolean) (określając true dla isThreadSafe) i konstruktora Lazy<T>(Func<T>, LazyThreadSafetyMode) (określając LazyThreadSafetyMode.ExecutionAndPublication dla mode). Aby przełączyć się na inny konstruktor, po prostu zmień, które konstruktory są komentowane.

Aby zapoznać się z przykładem buforowania wyjątków przy użyciu tych samych konstruktorów, zobacz konstruktor Lazy<T>(Func<T>).

W przykładzie zdefiniowano klasę LargeObject, która zostanie zainicjowana z opóźnieniem przez jeden z kilku wątków. Cztery kluczowe sekcje kodu ilustrują tworzenie inicjatora, metodę fabryki, rzeczywistą inicjację i konstruktor klasy LargeObject, która wyświetla komunikat podczas tworzenia obiektu. Na początku metody Main przykład tworzy bezpieczny wątkowo inicjator leniwy dla 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)

Metoda factory przedstawia tworzenie obiektu z symbolem zastępczym na potrzeby dalszej inicjalizacji:

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

Należy pamiętać, że pierwsze dwie sekcje kodu można połączyć przy użyciu funkcji lambda, jak pokazano poniżej:

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)

Przykład wstrzymuje się, aby wskazać, że nieokreślony okres może upłynąć przed wystąpieniem opóźnionego inicjowania. Po naciśnięciu Enter przykład tworzy i uruchamia trzy wątki. Metoda ThreadProc używana przez wszystkie trzy wątki wywołuje właściwość Value. Przy pierwszym wystąpieniu jest tworzone wystąpienie 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

Konstruktor klasy LargeObject, która zawiera ostatnią sekcję klucza kodu, wyświetla komunikat i rejestruje tożsamość wątku inicjowania. Dane wyjściowe z programu są wyświetlane na końcu pełnej listy kodu.

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

Nuta

Dla uproszczenia w tym przykładzie użyto globalnego wystąpienia Lazy<T>, a wszystkie metody są static (Shared w Visual Basic). Nie są to wymagania dotyczące używania leniwej inicjalizacji.

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
'

Uwagi

Użyj opóźnionego inicjowania, aby odroczyć tworzenie dużego lub intensywnie korzystającego z zasobów obiektu lub wykonanie zadania intensywnie korzystającego z zasobów, szczególnie w przypadku, gdy takie tworzenie lub wykonywanie może nie wystąpić w okresie istnienia programu.

Aby przygotować się do inicjowania z opóźnieniem, należy utworzyć wystąpienie Lazy<T>. Argument typu tworzonego obiektu Lazy<T> określa typ obiektu, który chcesz zainicjować z opóźnieniem. Konstruktor używany do utworzenia obiektu Lazy<T> określa cechy inicjowania. Inicjowanie z opóźnieniem występuje przy pierwszym uzyskiwaniu dostępu do właściwości Lazy<T>.Value.

W większości przypadków wybór konstruktora zależy od odpowiedzi na dwa pytania:

  • Czy obiekt zainicjowany z opóźnieniem będzie uzyskiwany z więcej niż jednego wątku? Jeśli tak, obiekt Lazy<T> może utworzyć go w dowolnym wątku. Można użyć jednego z prostych konstruktorów, których domyślne zachowanie polega na utworzeniu obiektu Lazy<T> bezpiecznego wątkowo, tak aby tylko jedno wystąpienie leniwie utworzonego obiektu było tworzone bez względu na to, ile wątków próbuje uzyskać do niego dostęp. Aby utworzyć obiekt Lazy<T>, który nie jest bezpieczny wątkiem, należy użyć konstruktora, który umożliwia określenie bezpieczeństwa wątku.

    Ostrożność

    Zabezpieczanie wątku obiektu Lazy<T> nie chroni obiektu zainicjowanego z opóźnieniem. Jeśli wiele wątków może uzyskać dostęp do obiektu zainicjowanego z opóźnieniem, należy zapewnić bezpieczeństwo jego właściwości i metod dla dostępu wielowątkowego.

  • Czy leniwa inicjalizacja wymaga dużo kodu lub czy zainicjowany obiekt z opóźnieniem ma konstruktor bez parametrów, który robi wszystko, czego potrzebujesz i nie zgłasza wyjątków? Jeśli musisz napisać kod inicjowania lub jeśli należy obsłużyć wyjątki, użyj jednego z konstruktorów, który przyjmuje metodę fabryki. Napisz kod inicjowania w metodzie factory.

W poniższej tabeli przedstawiono, który konstruktor należy wybrać, na podstawie tych dwóch czynników:

Dostęp do obiektu będzie uzyskiwany przez program Jeśli nie jest wymagany kod inicjowania (konstruktor bez parametrów), użyj polecenia Jeśli wymagany jest kod inicjowania, użyj polecenia
Wiele wątków Lazy<T>() Lazy<T>(Func<T>)
Jeden wątek Lazy<T>(Boolean) z isThreadSafe ustawioną na wartość false. Lazy<T>(Func<T>, Boolean) z isThreadSafe ustawioną na wartość false.

Aby określić metodę fabryki, można użyć wyrażenia lambda. Spowoduje to zachowanie całego kodu inicjowania w jednym miejscu. Wyrażenie lambda przechwytuje kontekst, w tym wszelkie argumenty przekazywane do konstruktora obiektu zainicjowanego z opóźnieniem.

buforowanie wyjątków Podczas korzystania z metod fabrycznych wyjątki są buforowane. Oznacza to, że jeśli metoda fabryki zgłasza wyjątek przy pierwszym próbie uzyskania dostępu do właściwości Value obiektu Lazy<T>, ten sam wyjątek jest zgłaszany podczas każdej kolejnej próby. Dzięki temu każde wywołanie właściwości Value daje ten sam wynik i pozwala uniknąć drobnych błędów, które mogą wystąpić, jeśli różne wątki uzyskują różne wyniki. Lazy<T> oznacza rzeczywiste T, które w przeciwnym razie zostałyby zainicjowane w pewnym wcześniejszym punkcie, zwykle podczas uruchamiania. Awaria w tym wcześniejszym punkcie jest zwykle śmiertelna. Jeśli istnieje możliwość wystąpienia awarii możliwej do odzyskania, zalecamy utworzenie logiki ponawiania prób w procedurze inicjowania (w tym przypadku metody fabryki), tak jak w przypadku braku leniwego inicjowania.

Alternatywa do blokowania W niektórych sytuacjach warto uniknąć narzutu domyślnego zachowania blokowania obiektu Lazy<T>. W rzadkich sytuacjach może wystąpić potencjał zakleszczenia. W takich przypadkach można użyć konstruktora Lazy<T>(LazyThreadSafetyMode) lub Lazy<T>(Func<T>, LazyThreadSafetyMode) i określić LazyThreadSafetyMode.PublicationOnly. Dzięki temu obiekt Lazy<T> może utworzyć kopię obiektu zainicjowanego z opóźnieniem na każdym z kilku wątków, jeśli wątki wywołają właściwość Value jednocześnie. Obiekt Lazy<T> gwarantuje, że wszystkie wątki używają tego samego wystąpienia obiektu zainicjowanego z opóźnieniem i odrzuca wystąpienia, które nie są używane. W związku z tym koszt zmniejszenia obciążenia związanego z blokowaniem polega na tym, że program może czasami tworzyć i odrzucać dodatkowe kopie kosztownego obiektu. W większości przypadków jest to mało prawdopodobne. Przykłady konstruktorów Lazy<T>(LazyThreadSafetyMode) i Lazy<T>(Func<T>, LazyThreadSafetyMode) pokazują to zachowanie.

Ważny

Po określeniu LazyThreadSafetyMode.PublicationOnlywyjątki nigdy nie są buforowane, nawet jeśli określisz metodę fabryki.

Konstruktory równoważne Oprócz umożliwienia korzystania z LazyThreadSafetyMode.PublicationOnlykonstruktory Lazy<T>(LazyThreadSafetyMode) i Lazy<T>(Func<T>, LazyThreadSafetyMode) mogą duplikować funkcjonalność innych konstruktorów. W poniższej tabeli przedstawiono wartości parametrów, które generują równoważne zachowanie.

Aby utworzyć obiekt Lazy<T>, który jest W przypadku konstruktorów, które mają parametr LazyThreadSafetyModemode, ustaw mode na W przypadku konstruktorów, które mają parametr isThreadSafe logiczny, ustaw wartość isThreadSafe W przypadku konstruktorów bez parametrów bezpieczeństwa wątku
W pełni bezpieczne wątki; używa blokady, aby upewnić się, że tylko jeden wątek inicjuje wartość. ExecutionAndPublication true Wszystkie takie konstruktory są w pełni bezpieczne wątkami.
Nie można bezpiecznie wątku. None false Nie dotyczy.
W pełni bezpieczne wątki; wątki ścigają się, aby zainicjować wartość. PublicationOnly Nie dotyczy. Nie dotyczy.

Inne możliwości Aby uzyskać informacje o używaniu Lazy<T> z polami statycznymi wątków lub jako magazynem zapasowym właściwości, zobacz Leniwe inicjowanie.

Konstruktory

Lazy<T>()

Inicjuje nowe wystąpienie klasy Lazy<T>. W przypadku wystąpienia opóźnionego inicjowania używany jest konstruktor bez parametrów typu docelowego.

Lazy<T>(Boolean)

Inicjuje nowe wystąpienie klasy Lazy<T>. Gdy wystąpi opóźnienie inicjowania, używany jest konstruktor bez parametrów typu docelowego i określony tryb inicjowania.

Lazy<T>(Func<T>)

Inicjuje nowe wystąpienie klasy Lazy<T>. Gdy wystąpi opóźnienie inicjowania, używana jest określona funkcja inicjowania.

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

Inicjuje nowe wystąpienie klasy Lazy<T>. Gdy wystąpi opóźnienie inicjowania, używana jest określona funkcja inicjowania i tryb inicjowania.

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

Inicjuje nowe wystąpienie klasy Lazy<T>, która używa określonej funkcji inicjowania i trybu bezpieczeństwa wątków.

Lazy<T>(LazyThreadSafetyMode)

Inicjuje nowe wystąpienie klasy Lazy<T>, która używa konstruktora bez parametrów T i określonego trybu bezpieczeństwa wątków.

Lazy<T>(T)

Inicjuje nowe wystąpienie klasy Lazy<T>, która używa wstępnie zdefiniowanej wartości.

Właściwości

IsValueCreated

Pobiera wartość wskazującą, czy dla tego wystąpienia Lazy<T> utworzono wartość.

Value

Pobiera leniwie zainicjowaną wartość bieżącego wystąpienia Lazy<T>.

Metody

Equals(Object)

Określa, czy określony obiekt jest równy bieżącemu obiektowi.

(Odziedziczone po Object)
GetHashCode()

Służy jako domyślna funkcja skrótu.

(Odziedziczone po Object)
GetType()

Pobiera Type bieżącego wystąpienia.

(Odziedziczone po Object)
MemberwiseClone()

Tworzy płytkią kopię bieżącego Object.

(Odziedziczone po Object)
ToString()

Tworzy i zwraca reprezentację ciągu właściwości Value dla tego wystąpienia.

Dotyczy

Bezpieczeństwo wątkowe

Domyślnie wszystkie publiczne i chronione elementy członkowskie klasy Lazy<T> są bezpieczne wątkowo i mogą być używane współbieżnie z wielu wątków. Te gwarancje bezpieczeństwa wątków można usunąć opcjonalnie i na wystąpienie przy użyciu parametrów konstruktorów typu.

Zobacz też