Как работает Lazy<T> и когда его применять?
Хочу, чтобы тяжёлый объект создавался только при первом обращении. Слышал про Lazy<T>, но не до конца понял, как он работает и потокобезопасен ли он.
Lazy<int> value = new Lazy<int>(() => Compute());
3 ответа
Lazy<T> откладывает создание объекта до первого обращения к свойству .Value. Фабрика вызывается ровно один раз, результат кэшируется.
Lazy<int> lazy = new Lazy<int>(() =>
{
Console.WriteLine("Вычисляем...");
return 42;
});
Console.WriteLine("До обращения"); // фабрика ещё не вызвана
Console.WriteLine(lazy.Value); // "Вычисляем..." + 42
Console.WriteLine(lazy.Value); // просто 42 (кэш)
Про потокобезопасность: по умолчанию (LazyThreadSafetyMode.ExecutionAndPublication) Lazy<T> потокобезопасен — даже при одновременном обращении из нескольких потоков фабрика выполнится один раз. Можно ослабить через конструктор с bool isThreadSafe или enum-режимом, если синхронизация не нужна.
Типичные сценарии: дорогая инициализация (загрузка конфига, подключение), синглтоны, разрыв тяжёлых зависимостей.
Удобный приём — ленивый синглтон:
public sealed class Config
{
private static readonly Lazy<Config> _instance =
new Lazy<Config>(() => new Config());
public static Config Instance => _instance.Value;
private Config() { /* тяжёлая загрузка */ }
}
Объект создастся при первом Config.Instance и будет потокобезопасным из коробки.
Осторожно с исключениями в фабрике: в режиме по умолчанию исключение кэшируется, и каждый следующий .Value будет выбрасывать его же. Если нужно повторять попытки — используйте LazyThreadSafetyMode.PublicationOnly.