Недостатки Lazy <T>? - программирование
Подтвердить что ты не робот

Недостатки Lazy <T>?

Недавно я начал использовать Lazy на протяжении всего моего приложения, и мне было интересно, есть ли какие-то очевидные негативные аспекты, которые мне нужно учитывать при использовании Lazy<T>?

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

4b9b3361

Ответ 1

Я немного расскажу о своем комментарии, который гласит:

Я только начал использовать Lazy и обнаружил, что он часто указывает плохой дизайн; или лень со стороны программиста. Кроме того, один Недостатком является то, что вы должны быть более бдительными переменные и создать правильные закрытия.

Например, я использовал Lazy<T> для создания страниц, которые пользователь может видеть в моем (сеансовом) приложении MVC. Это управляющий мастер, поэтому пользователь может захотеть перейти к случайному предыдущему шагу. Когда выполняется рукопожатие, массив объектов Lazy<Page> разбивается на куски, и если пользователь указывает как шаг, то оценивается точная страница. Я нахожу, что он обеспечивает хорошую производительность, но есть некоторые аспекты, которые мне не нравятся, например, многие из моих конструкций foreach теперь выглядят следующим образом:

foreach(var something in somethings){
     var somethingClosure = something;
     list.Add(new Lazy<Page>(() => new Page(somethingClosure));
} 

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

С другой стороны, это может указывать на то, что программист является Lazy<Programmer>, в том смысле, что вы предпочитаете не думать через свою программу сейчас, а вместо этого позволяйте правильной логике оценивать, когда это необходимо, как в примере в мой случай - вместо того, чтобы строить этот массив, я мог бы просто выяснить, какая именно эта запрошенная страница будет; но я решил быть ленивым и делать все в заходе.

ИЗМЕНИТЬ

Мне кажется, что Lazy<T> также имеет несколько особенностей при работе с concurrency. Например, для некоторых сценариев существует ThreadLocal<T> и несколько конфигураций флагов для вашего конкретного многопоточного сценария. Вы можете прочитать больше на msdn.

Ответ 2

Здесь не совсем негативный аспект, а вот для ленивых людей:).

Ленивые инициализаторы похожи на статические инициализаторы. Они получают один раз. Если выбрано исключение, исключение кэшируется и последующие вызовы .Value будут вызывать одно и то же исключение. Это по дизайну и упоминается в документах... http://msdn.microsoft.com/en-us/library/dd642329.aspx:

Исключения, вызванные значениемFactory, кэшируются.

Следовательно, код, как показано ниже, никогда не вернет значение:

bool firstTime = true;
Lazy<int> lazyInt = new Lazy<int>(() =>
{
    if (firstTime)
    {
        firstTime = false;
        throw new Exception("Always throws exception the very first time.");
    }

    return 21;
});

int? val = null;
while (val == null)
{
    try
    {
        val = lazyInt.Value;
    }
    catch
    {

    }
}

Ответ 3

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

Например, я не понимаю смысла в примере выбора страницы в одном из других ответов. Использование списка Lazy для выбора одного элемента может быть выполнено с помощью списка или словаря делегатов напрямую без использования Lazy или с простым оператором switch.

Таким образом, наиболее очевидными альтернативами являются

  • прямая инстанция для дешевых структур данных или структур, которые в любом случае необходимы
  • делегаты для вещей, которые необходимы от нуля до нескольких раз в некотором алгоритме
  • некоторая структура кеширования для элементов, которые должны освобождать память, когда они не используются некоторое время
  • какая-то "будущая" структура, такая как "Задача", которая уже может начать инициализацию асинхронно до фактического использования, потребляющего простое процессорное время в случаях, когда вероятность достаточно высока, что структура потребуется позже

В отличие от этого, Lazy часто подходит, когда

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

Ответ 4

Я пришел использовать Lazy<T> главным образом из-за его возможностей concurrency при загрузке ресурсов из базы данных. Таким образом, я избавился от объектов блокировки и возможных шаблонов блокировки. В моем случае ConcurrentDictionary + Lazy в качестве значения, сделанного моим днем, спасибо @Reed Copsey и его сообщение в блоге

Это выглядит следующим образом. Вместо вызова:

MyValue value = dictionary.GetOrAdd(
                             key, 
                             () => new MyValue(key));

Вместо этого мы будем использовать ConcurrentDictionary > и написать:

MyValue value = dictionary.GetOrAdd(
                             key, 
                             () => new Lazy<MyValue>(
                                 () => new MyValue(key)))
                          .Value;

Отмечено, что минус Lazy<T>.

Ответ 5

Как и во всем, Lazy<T> может использоваться для добра или для зла, поэтому недостаток: при неправильном использовании он может вызвать путаницу и разочарование. Тем не менее, ленивая схема инициализации существует уже много лет, и теперь, когда .NET BCL имеет разработчиков, разработчикам не нужно снова изобретать колесо. Что еще, MEF любит Lazy.

Ответ 6

Что именно вы имеете в виду "во всем моем приложении"?

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

С другой стороны, зачем использовать Lazy здесь? В большинстве случаев вы можете просто вызвать метод вместо lazy.Value, и это не имеет никакого значения. НО для программиста это проще и понятнее, что происходит в этой ситуации без Lazy.

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

Ответ 7

Lazy используется для сохранения ресурсов, в то время как они не нужны. Этот шаблон довольно хорош, но реализация может быть бесполезной.

Чем больше ресурс, тем полезнее этот шаблон.

Отказ от использования класса Lazy - непрозрачность использования. Действительно, вы должны поддерживать повсюду дополнительную косвенность (.Value). Когда вам просто нужен экземпляр реального типа, он вынужден загружаться, даже если вам не нужно использовать его напрямую.

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

Если у вас есть реальная прозрачная реализация (например, с использованием прокси-шаблона), она избавляется от disavantage и может быть очень полезной во многих случаях.

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