Lazy<T> Класс

Определение

Обеспечивает поддержку отложенной инициализации.

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)

Параметры типа

T

Тип объекта с отложенной инициализацией.

Наследование
Lazy<T>
Производный
Атрибуты

Примеры

В следующем примере показано использование класса для предоставления отложенной Lazy<T> инициализации с доступом из нескольких потоков.

Примечание

В примере используется Lazy<T>(Func<T>) конструктор. Он также демонстрирует использование конструктора Lazy<T>(Func<T>, Boolean) (указывающего true для isThreadSafe) и Lazy<T>(Func<T>, LazyThreadSafetyMode) конструктора (указывающего LazyThreadSafetyMode.ExecutionAndPublication для mode). Чтобы переключиться на другой конструктор, просто измените, какие конструкторы закомментированы.

Пример, демонстрирующий кэширование исключений с помощью одних и того же конструктора, см. в конструкторе Lazy<T>(Func<T>) .

В этом примере определяется класс LargeObject, для которого будет выполняться отложенная инициализация одним из нескольких потоков. Четыре ключевых раздела кода иллюстрируют создание инициализатора, метода фабрики, фактической инициализации и конструктора LargeObject класса, который отображает сообщение при создании объекта. В начале метода Main в этом примере создается потокобезопасный отложенный инициализатор для класса 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)

Метод фабрики показывает создание объекта с заполнителем для дальнейшей инициализации:

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

Обратите внимание, что первые два раздела кода можно объединить с помощью лямбда-функции, как показано ниже:

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)

Пример приостанавливается, чтобы указать, что неопределенный период может пройти до отложенной инициализации. При нажатии клавиши ВВОД пример создает и запускает три потока. Метод ThreadProc , используемый всеми тремя потоками, вызывает Value свойство. При первом создании экземпляра 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

Конструктор LargeObject класса, который включает последний раздел ключа кода, отображает сообщение и записывает удостоверение инициализации потока. Выходные данные программы отображаются в конце полного описания кода.

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

Примечание

Для простоты в этом примере используется глобальный экземпляр класса Lazy<T>, а все методы объявлены как static (Shared в Visual Basic). Это не является обязательными требованиями для использования отложенной инициализации.

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
'

Комментарии

Используйте отложенную инициализацию для отсрочки создания большого или ресурсоемкого объекта или выполнения ресурсоемкой задачи, особенно если такое создание или выполнение может не произойти в течение времени существования программы.

Чтобы подготовиться к отложенной инициализации, создайте экземпляр Lazy<T>. Аргумент типа создаваемого Lazy<T> объекта указывает тип объекта, который требуется инициализировать лениво. Конструктор, используемый для создания Lazy<T> объекта, определяет характеристики инициализации. Отложенная инициализация производится при первом обращении к свойству Lazy<T>.Value.

В большинстве случаев выбор конструктора зависит от ответов на два вопроса:

  • Будет ли доступ к лениво инициализированному объекту из нескольких потоков? В этом Lazy<T> случае объект может создать его в любом потоке. Можно использовать один из простых конструкторов, поведение которых по умолчанию — создать потокобезопасный Lazy<T> объект, чтобы создать к нему только один экземпляр отложенного экземпляра объекта независимо от того, сколько потоков пытается получить к нему доступ. Чтобы создать Lazy<T> объект, который не является потокобезопасной, необходимо использовать конструктор, который позволяет указать отсутствие безопасности потока.

    Внимание!

    Lazy<T> Обеспечение безопасности потока объекта не защищает лениво инициализированный объект. Если несколько потоков могут получить доступ к лениво инициализированному объекту, необходимо сделать его свойства и методы безопасными для многопоточного доступа.

  • Требуется ли отложенная инициализация большого количества кода или имеет ли лениво инициализированный объект конструктор без параметров, который делает все необходимое и не создает исключения? Если необходимо написать код инициализации или если необходимо обработать исключения, используйте один из конструкторов, который принимает метод фабрики. Напишите код инициализации в методе фабрики.

В следующей таблице показано, какой конструктор следует выбрать на основе этих двух факторов:

Доступ к объекту будет осуществляться с помощью Если код инициализации не требуется (конструктор без параметров), используйте Если требуется код инициализации, используйте
Несколько потоков Lazy<T>() Lazy<T>(Func<T>)
Один поток Lazy<T>(Boolean) с параметром isThreadSafe со значением false. Lazy<T>(Func<T>, Boolean) с параметром isThreadSafe со значением false.

Для указания метода фабрики можно использовать лямбда-выражение. При этом весь код инициализации сохраняется в одном месте. Лямбда-выражение фиксирует контекст, включая все аргументы, которые передаются конструктору неявно инициализированного объекта.

Кэширование исключений При использовании методов фабрики исключения кэшируются. То есть, если метод фабрики создает исключение при первой попытке потока получить доступ к Value свойству Lazy<T> объекта, то при каждой последующей попытке возникает то же исключение. Это гарантирует, что каждый вызов Value свойства создает один и тот же результат и позволяет избежать незначительных ошибок, которые могут возникнуть, если разные потоки получают разные результаты. Он Lazy<T> означает фактическую T , которая в противном случае была бы инициализирована в какой-то более ранний момент, как правило, во время запуска. Сбой в этой более ранней точке обычно неустраним. Если существует вероятность сбоя, допускающего восстановление, рекомендуется создать логику повторных попыток в подпрограмму инициализации (в данном случае — метод фабрики), так же, как если бы вы не использовали отложенную инициализацию.

Альтернатива блокировке В некоторых ситуациях может потребоваться избежать дополнительных расходов Lazy<T> на поведение блокировки объекта по умолчанию. В редких ситуациях может возникнуть вероятность взаимоблокировок. В таких случаях можно использовать Lazy<T>(LazyThreadSafetyMode) конструктор или Lazy<T>(Func<T>, LazyThreadSafetyMode) конструктор и указать LazyThreadSafetyMode.PublicationOnly. Это позволяет Lazy<T> объекту создать копию лениво инициализированного объекта на каждом из нескольких потоков, если потоки вызывают Value свойство одновременно. Объект Lazy<T> гарантирует, что все потоки используют один и тот же экземпляр лениво инициализированного объекта и отменяют экземпляры, которые не используются. Таким образом, затраты на снижение затрат на блокировку заключается в том, что программа иногда может создавать и удалять дополнительные копии дорогостоящего объекта. В большинстве случаев это маловероятно. Примеры для Lazy<T>(LazyThreadSafetyMode) конструкторов демонстрируют Lazy<T>(Func<T>, LazyThreadSafetyMode) это поведение.

Важно!

При указании LazyThreadSafetyMode.PublicationOnlyисключения никогда не кэшируются, даже если указан метод фабрики.

Эквивалентные конструкторы Помимо включения использования LazyThreadSafetyMode.PublicationOnlyконструкторы Lazy<T>(LazyThreadSafetyMode) могут Lazy<T>(Func<T>, LazyThreadSafetyMode) дублировать функциональные возможности других конструкторов. В следующей таблице показаны значения параметров, которые создают эквивалентное поведение.

Lazy<T> Создание объекта, который является Для конструкторов с параметром LazyThreadSafetyMode mode задайте значение mode For constructors that have a Boolean isThreadSafe parameter, set isThreadSafe to Для конструкторов без параметров безопасности потока
Полностью потокобезопасный; использует блокировку, чтобы гарантировать, что только один поток инициализирует значение. ExecutionAndPublication true Все такие конструкторы полностью потокобезопасны.
Не потокобезопасно. None false Неприменимо.
Полностью потокобезопасный; потоки гонки для инициализации значения. PublicationOnly Неприменимо. Неприменимо.

Другие возможности Сведения об использовании Lazy<T> с потоком статических полей или резервном хранилище свойств см. в разделе " Отложенная инициализация".

Конструкторы

Lazy<T>()

Инициализирует новый экземпляр класса Lazy<T>. При неактивной инициализации используется конструктор целевого типа без параметров.

Lazy<T>(Boolean)

Инициализирует новый экземпляр класса Lazy<T>. При неактивной инициализации используются конструктор целевого типа без параметров и заданный режим инициализации.

Lazy<T>(Func<T>)

Инициализирует новый экземпляр класса Lazy<T>. Когда происходит отложенная инициализация, используется заданная функция инициализации.

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

Инициализирует новый экземпляр класса Lazy<T>. Когда происходит отложенная инициализация, используются заданные функция инициализации и режим инициализации.

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

Инициализирует новый экземпляр класса Lazy<T>, который использует заданную функцию инициализации и потокобезопасный режим.

Lazy<T>(LazyThreadSafetyMode)

Инициализирует новый экземпляр класса Lazy<T>, который использует конструктор T без параметров и заданный потокобезопасный режим.

Lazy<T>(T)

Инициализирует новый экземпляр класса Lazy<T>, использующий предварительно инициализированное заданное значение.

Свойства

IsValueCreated

Получает значение, которое показывает, создано ли значение для этого экземпляра Lazy<T>.

Value

Получает значение с отложенной инициализацией текущего экземпляра Lazy<T>.

Методы

Equals(Object)

Определяет, равен ли указанный объект текущему объекту.

(Унаследовано от Object)
GetHashCode()

Служит хэш-функцией по умолчанию.

(Унаследовано от Object)
GetType()

Возвращает объект Type для текущего экземпляра.

(Унаследовано от Object)
MemberwiseClone()

Создает неполную копию текущего объекта Object.

(Унаследовано от Object)
ToString()

Создает и возвращает строковое представление свойства Value для данного экземпляра.

Применяется к

Потокобезопасность

По умолчанию все открытые и защищенные члены Lazy<T> класса являются потокобезопасными и могут использоваться одновременно из нескольких потоков. Эти гарантии безопасности потоков могут быть удалены при необходимости и для каждого экземпляра, используя параметры для конструкторов типа.

См. также раздел