Недостатки Ленивых <T>? - PullRequest
       38

Недостатки Ленивых <T>?

47 голосов
/ 27 сентября 2011

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

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

Ответы [ 7 ]

18 голосов
/ 27 сентября 2011

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

Я только начал использовать 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> также имеет несколько особенностей при работе с параллелизмом,Например, есть ThreadLocal<T> для некоторых сценариев и несколько конфигураций флагов для вашего конкретного многопоточного сценария.Вы можете прочитать больше на msdn .

8 голосов
/ 21 декабря 2012

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

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

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

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

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

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

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

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

Исключения, которые выбрасываются valueFactory, кэшируются.

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

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
    {

    }
}
5 голосов
/ 21 декабря 2012

Я стал использовать Lazy<T> в основном из-за его возможностей параллелизма при загрузке ресурсов из базы данных.Таким образом я избавился от объектов блокировки и спорных шаблонов блокировки.В моем случае 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> пока не замечено.

4 голосов
/ 27 сентября 2011

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

2 голосов
/ 08 января 2015

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

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

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

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

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

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

2 голосов
/ 27 сентября 2011

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

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

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

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

...