Когда следует использовать Lazy <T>?

Я нашел эту статью о Lazy: Лень в С# 4.0 - ленивый

Какова наилучшая практика для лучшей производительности с использованием объектов Lazy? Может ли кто-нибудь указать мне на практическое применение в реальном приложении? Другими словами, когда я должен его использовать?

Ответ 1

Обычно вы используете его, когда хотите что-то создать в первый раз, когда он действительно используется. Это задерживает затраты на его создание до тех пор, пока/когда это понадобится, а не всегда будет стоить.

Обычно это предпочтительнее, когда объект может использоваться или не использоваться, а стоимость его создания нетривиальна.

Ответ 2

Вы должны стараться избегать использования Singletons, но если вам когда-либо понадобится, Lazy<T> упрощает реализацию ленивых, потокобезопасных синглетонов:

public sealed class Singleton
{
    // Because Singleton constructor is private, we must explicitly
    // give the Lazy<Singleton> a delegate for creating the Singleton.
    static readonly Lazy<Singleton> instanceHolder =
        new Lazy<Singleton>(() => new Singleton());

    Singleton()
    {
        // Explicit private constructor to prevent default public constructor.
        ...
    }

    public static Singleton Instance => instanceHolder.Value;
}

Ответ 3

Хороший пример реального мира, где ленивая загрузка пригодится, - это ORM (Meterers Relation), такие как Entity Framework и NHibernate.

Скажите, что у вас есть объект Клиент, у которого есть свойства для имени, номера телефона и ордеров. Имя и PhoneNumber являются регулярными строками, но Orders - это свойство навигации, которое возвращает список каждого заказа, который когда-либо делал клиент.

Вы часто можете захотеть пройти через всех своих клиентов и получить их имя и номер телефона, чтобы позвонить им. Это очень быстрая и простая задача, но представьте, что каждый раз, когда вы создавали клиента, он автоматически отправлялся и выполнял сложное соединение, чтобы вернуть тысячи заказов. Хуже всего то, что вы даже не собираетесь использовать заказы, поэтому это полная трата ресурсов!

Это идеальное место для ленивой загрузки, потому что, если свойство Order является ленивым, оно не будет получать весь заказ клиента, если вы на самом деле не нуждаетесь в них. Вы можете перечислять объекты Клиента, получая только свое имя и номер телефона, в то время как свойство Order терпеливо спит, готовое к тому, когда оно вам нужно.

Ответ 4

Я рассматриваю возможность использования свойств Lazy<T>, чтобы улучшить производительность моего собственного кода (и немного узнать об этом). Я пришел сюда, чтобы найти ответы на вопрос о том, когда его использовать, но кажется, что везде, куда я иду, есть такие фразы, как:

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

из MSDN Lazy <T> Класс

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

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

Описание

В моем случае мне было особенно интересно узнать, может ли Lazy Properties помочь улучшить часть моего кода, который делает большую интерполяцию (большая часть ее не используется), и поэтому я создал тест, сравнивающий 3 подхода.

Я создал отдельный тестовый класс с 20 тестовыми свойствами (позволяет называть их t-свойствами) для каждого подхода.

  • GetInterp Class: Запускает линейную интерполяцию каждый раз, когда получен t-свойство.
  • Класс InitInterp: Инициализирует t-свойства, запустив линейную интерполяцию для каждого из конструкторов. Результат просто возвращает двойной.
  • Класс InitLazy: Устанавливает t-свойства как Lazy-свойства, так что линейная интерполяция выполняется один раз, когда свойство сначала получает. Последующие get должны просто вернуть уже вычисленный double.

Результаты теста измеряются в мс и составляют в среднем 50 экземпляров или 20 свойств. Каждый тест затем выполнялся 5 раз.

Результаты теста 1: Мгновенное действие (в среднем 50 экземпляров)

Class      1        2        3        4        5        Avg       %
------------------------------------------------------------------------
GetInterp  0.005668 0.005722 0.006704 0.006652 0.005572 0.0060636 6.72
InitInterp 0.08481  0.084908 0.099328 0.098626 0.083774 0.0902892 100.00
InitLazy   0.058436 0.05891  0.068046 0.068108 0.060648 0.0628296 69.59

Результаты теста 2: First Get (в среднем 20 свойств)

Class      1        2        3        4        5        Avg       %
------------------------------------------------------------------------
GetInterp  0.263    0.268725 0.31373  0.263745 0.279675 0.277775 54.38
InitInterp 0.16316  0.161845 0.18675  0.163535 0.173625 0.169783 33.24
InitLazy   0.46932  0.55299  0.54726  0.47878  0.505635 0.510797 100.00

Результаты теста 3: Второй Получить (в среднем 20 свойств)

Class      1        2        3        4        5        Avg       %
------------------------------------------------------------------------
GetInterp  0.08184  0.129325 0.112035 0.097575 0.098695 0.103894 85.30
InitInterp 0.102755 0.128865 0.111335 0.10137  0.106045 0.110074 90.37
InitLazy   0.19603  0.105715 0.107975 0.10034  0.098935 0.121799 100.00

Наблюдения

GetInterp работает быстрее, чем ожидалось, потому что ничего не делает. InitLazy работает быстрее, чем InitInterp, предполагая, что накладные расходы при настройке ленивых свойств быстрее, чем мой расчет линейной интерполяции. Однако я немного смущен, потому что InitInterp должен делать 20 линейных интерполяций (для настройки его t-свойств), но для создания экземпляра (тест 1) требуется всего 0,09 мс, по сравнению с GetInterp, который занимает 0,28 мс сделать только одну линейную интерполяцию в первый раз (тест 2) и 0,1 мс сделать это второй раз (тест 3).

Требуется InitLazy почти в 2 раза дольше, чем GetInterp, чтобы получить свойство в первый раз, тогда как InitInterp является самым быстрым, поскольку оно заполняет его свойства во время создания экземпляра. (По крайней мере, это то, что он должен был сделать, но почему результат создания был намного быстрее, чем одна линейная интерполяция? Когда именно он делает эти интерполяции?)

К сожалению, похоже, что в моих тестах происходит автоматическая оптимизация кода. Для получения свойства в первый раз, как и во второй раз, требуется GetInterp, но он показывает более чем в 2 раза быстрее. Похоже, что эта оптимизация также влияет на другие классы, поскольку все они занимают примерно такое же количество времени для теста 3. Однако такие оптимизации могут также иметь место в моем собственном производственном коде, который также может быть важным соображением.

Выводы

В то время как некоторые результаты ожидаются, есть также некоторые очень интересные неожиданные результаты, вероятно, из-за оптимизации кода. Даже для классов, которые выглядят так, как будто они выполняют большую работу в конструкторе, результаты создания экземпляра показывают, что они могут быть очень быстро созданы по сравнению с получением двойного свойства. Хотя эксперты в этой области могут более подробно комментировать и исследовать, мое личное мнение состоит в том, что мне нужно снова выполнить этот тест, но на моем производственном коде, чтобы выяснить, какие там могут быть и какие оптимизации. Тем не менее, я ожидаю, что InitInterp может быть путем.

Ответ 5

Чтобы указать на пример, отправленный Мэтью

public sealed class Singleton
{
    // Because Singleton constructor is private, we must explicitly
    // give the Lazy<Singleton> a delegate for creating the Singleton.
    private static readonly Lazy<Singleton> instanceHolder =
        new Lazy<Singleton>(() => new Singleton());

    private Singleton()
    {
        ...
    }

    public static Singleton Instance
    {
        get { return instanceHolder.Value; }
    }
}

до того, как Lazy родился, мы сделали бы это так:

private static object lockingObject = new object();
public static LazySample InstanceCreation()
{
    if(lazilyInitObject == null)
    {
         lock (lockingObject)
         {
              if(lazilyInitObject == null)
              {
                   lazilyInitObject = new LazySample ();
              }
         }
    }
    return lazilyInitObject ;
}

Ответ 6

Из MSDN:

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

В дополнение к ответу Джеймса Майкла Харе, Lazy обеспечивает потокобезопасную инициализацию вашей ценности. Взгляните на LazyThreadSafetyMode перечисление записи MSDN, описывающее различные типы режимов безопасности потоков для этого класса.