Коли я повинен використовувати Lazy <T>?


327

Я знайшов цю статтю про Lazy: Лінь в C # 4.0 - Ледачий

Яка найкраща практика мати найкращі показники роботи з використанням лінивих об'єктів? Чи може хтось вказати мені на практичне використання в реальній програмі? Іншими словами, коли я повинен його використовувати?


42
Замінює: get { if (foo == null) foo = new Foo(); return foo; }. І є мільйони можливих місць для його використання ...
Кірк Волл

57
Зверніть увагу, що get { if (foo == null) foo = new Foo(); return foo; }це не є безпечним для потоків, а Lazy<T>за замовчуванням є безпечним для потоків.
Метью

23
Від MSDN: ВАЖЛИВО: Ледача ініціалізація є безпечною для потоків, але вона не захищає об'єкт після створення. Ви повинні заблокувати об'єкт перед тим, як отримати доступ до нього, якщо тільки тип не є безпечним для потоків.
Pedro.The.Kid

Відповіді:


237

Зазвичай ви використовуєте його, коли хочете щось інстанціювати при першому використанні. Це затримує витрати на його створення до того часу, коли це потрібно, а не завжди.

Зазвичай це є кращим, коли об'єкт може бути використаний або не може використовуватися, а вартість його побудови нетривіальна.


121
чому б не ЗАВЖДИ використовувати Ледачий?
TruthOf42

44
При першому використанні це несе витрати і може використовувати для цього деякі замикаючі накладні витрати (або, якщо ні, жертвує безпекою нитки). Таким чином, його слід вибирати ретельно і не використовувати, якщо не потрібно.
Джеймс Майкл Заєць

3
Джеймс, чи не могли б ви розширити питання про ", а вартість побудови нетривіальна"? У моєму випадку в моєму класі є 19 властивостей, і в більшості випадків лише 2 або 3 будуть потрібні для перегляду. Тому я розглядаю можливість реалізації кожної власності з використанням Lazy<T>. Однак для створення кожної властивості я роблю лінійну інтерполяцію (або білінеарну інтерполяцію), яка є досить тривіальною, але має певні витрати. (Ви збираєтесь запропонувати мені піти та робити власний експеримент?)
Бен

3
Джеймс, приймаючи власні поради, я робив власний експеримент. Дивіться мій пост .
Бен

17
Ви можете ініціалізувати / інстанціювати все, що під час запуску системи, щоб запобігти затримці користувачів у системах з високою пропускною здатністю та низькою затримкою. Це лише одна з багатьох причин того, що «завжди» використовувати «Ледачий».
Деррік

126

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

public sealed class Singleton
{
    // Because Singleton's 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;
}

38
Я ненавиджу читання. Вам слід намагатися уникати використання Singletons, коли я їх використовую: D ... тепер мені потрібно дізнатися, чому я повинен намагатися уникати їх: D
Барт Калікто

24
Я припиняю використовувати Singletons, коли Microsoft перестане використовувати їх у своїх прикладах.
eaglei22

4
Я схильний не погоджуватися з думкою про необхідність уникати синглтонів. При дотриманні парадигми введення залежності це не повинно мати жодного значення. В ідеалі всі ваші залежності повинні бути створені лише один раз. Це зменшує тиск на ГК у сценаріях високого навантаження. Тому зробити їх синглтоном з самого класу - це добре. Більшість (якщо не всі) сучасні контейнери DI можуть працювати з ним будь-яким способом.
Лі Гріссом

1
Вам не доведеться використовувати одинарний візерунок, натомість використовуйте будь-який контейнер di, що конфігурує ваш клас для одиночного. Контейнер подбає про накладні витрати за вас.
VivekDev

Все має мету, є ситуації, коли одиночні - хороший підхід, і ситуації, коли цього немає :).
Хоуксей

86

Чудовий приклад реального світу, коли корисно завантажувати ледачий настрій, - це такі, як ОРМ (Object Relation Mappers), як Entity Framework та NHibernate.

Скажімо, у вас є юридична особа Клієнт, яка має властивості для імені, номера телефону та замовлень. Ім'я та Телефонний номер - це звичайні рядки, але Замовлення - це властивість навігації, яка повертає список кожного замовлення, яке клієнт коли-небудь робив.

Ви часто можете пройти всі клієнти та отримати їх ім’я та номер телефону, щоб зателефонувати. Це дуже швидке і просте завдання, але уявіть, що кожного разу, коли ви створюєте клієнта, він автоматично переходив і робив складний приєднання, щоб повернути тисячі замовлень. Найгірше, що ви навіть не збираєтесь користуватися замовленнями, тому це повна трата ресурсів!

Це ідеальне місце для ледачого завантаження, тому що якщо властивість «Замовлення» ліниво, воно не буде виконувати всі замовлення клієнта, якщо вони вам справді не потрібні. Ви можете перерахувати об'єкти Клієнта, отримуючи лише їх ім’я та номер телефону, поки властивість «Замовлення» терпляче спить, готовий до того, коли вам це потрібно.


34
Поганий приклад, оскільки таке ледаче завантаження зазвичай вже вбудоване в ORM. Ви не повинні починати додавати значення Lazy <T> у свої POCO, щоб отримати ледачу завантаження, але використовувати специфічний для ORM спосіб.
Диналон

56
@Dyna Цей приклад стосується вбудованого ледачого завантаження ORM, тому що я думаю, що це чітко і просто відображає корисність ледачого завантаження.
Деспертар

Так, якщо ви використовуєте Entity Framework, чи слід застосовувати власну ліниву? Або EF це робить для вас?
Zapnologica

7
@Zapnologica EF робить це все для вас за замовчуванням. Насправді, якщо ви хочете прагнути завантаження (протилежне ледачому завантаженню), ви повинні прямо сказати EF, використовуючи Db.Customers.Include("Orders"). Це призведе до того, що приєднання до замовлення буде виконуватися в той момент, а не при Customer.Ordersпершому використанні властивості. Ледаче завантаження також можна відключити через DbContext.
Деспертар

2
Насправді це хороший приклад, оскільки можна додати цю функціональність, використовуючи щось на зразок Dapper.
tbone

41

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

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

від класу MSDN Lazy <T>

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

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

Опис

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

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

  • Клас GetInterp: Запускає лінійну інтерполяцію щоразу, коли отримується t-властивість.
  • Клас InitInterp: ініціалізує t-властивості, запустивши лінійну інтерполяцію для кожного з конструкторів. Отримати лише повертає дубль.
  • Клас InitLazy: встановлює t-властивості як властивості Lazy, щоб лінійна інтерполяція була запущена один раз, коли властивість вперше отримана. Подальше отримання повинно просто повернути вже обчислений подвійний.

Результати тесту вимірюються в мс і становлять в середньому 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: Перше отримання (в середньому 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-властивості), але для отримання екземпляра потрібно лише 0,09 мс (тест 1), порівняно з GetInterpяким потрібно лише 0,28 мс, щоб зробити лише одну лінійну інтерполяцію перший раз (тест 2) і 0,1 мс, щоб зробити це вдруге (тест 3).

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

На жаль, схоже, що в моїх тестах відбувається деяка автоматична оптимізація коду. GetInterpЩоб отримати об’єкт нерухомості потрібно в один і той же час, як і вдруге, але він відображається як більш ніж у 2 рази швидше. Схоже, що така оптимізація впливає і на інші класи, оскільки для тестування 3. вони займають приблизно стільки ж часу, проте такі оптимізації можуть мати місце і в моєму виробничому коді, що також може бути важливим фактором.

Висновки

Хоча деякі результати, як очікується, є й дуже цікаві несподівані результати, ймовірно, завдяки оптимізації коду. Навіть для класів, схожих на те, що вони роблять багато роботи в конструкторі, результати інстанції показують, що вони все ще можуть бути дуже швидкими для створення, порівняно з отриманням подвійного властивості. Хоча фахівці в цій галузі, можливо, зможуть прокоментувати та дослідити більш ретельно, моє особисте відчуття полягає в тому, що мені потрібно зробити цей тест ще раз, але на своєму виробничому коді, щоб вивчити, які саме оптимізації можуть відбуватися і там. Однак я очікую, що це InitInterpможе бути шлях.


26
можливо, ви повинні опублікувати свій тестовий код для відтворення результатів, тому що, не знаючи свого коду, важко буде щось запропонувати
WiiMaxx

1
Я вважаю, що головний компроміс - між використанням пам'яті (ледачий) та використанням процесора (не лінивий). Оскільки lazy, щоб зробити кілька додаткових бухгалтерій, InitLazyбуло б використано більше пам'яті, ніж інші рішення. Він також може мати незначне враження від продуктивності для кожного доступу, хоча він перевіряє, чи має він вже значення чи ні; розумні хитрощі могли б усунути цей наклад, але це вимагало б спеціальної підтримки в IL. (Haskell робить це, роблячи кожне ледаче значення викликом функції; як тільки значення генерується, воно замінюється функцією, яка щоразу повертає це значення.)
jpaugh

14

Просто, щоб вказати на приклад, опублікований Метью

public sealed class Singleton
{
    // Because Singleton's 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; }
    }
}

до того, як народився Ледачий, ми зробили б це так:

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

6
Я завжди використовую для цього контейнер IoC.
Джовен

1
Я рішуче погоджуюся розглянути контейнер IoC для цього. Якщо ви хочете, щоб простий ледачий ініціалізований об'єкт синглтон також врахував, що якщо вам не потрібно, щоб це було безпечно для потоків, виконайте це вручну з програмою If, якщо це найкраще, враховуючи ефективність роботи того, як обробляє себе Lazy.
Thulani Chivandikwa

12

Від MSDN:

Використовуйте екземпляр Lazy, щоб відкласти створення великого або ресурсомісткого об'єкта або виконання ресурсомісткого завдання, особливо коли таке створення або виконання може не відбуватися протягом життя програми.

Окрім відповіді Джеймса Майкла Зайця, Lazy надає безпечну ініціалізацію вашої вартості. Погляньте на запис MSDN з перерахуванням LazyThreadSafetyMode, що описує різні типи режимів безпеки потоку для цього класу.


-2

Ви повинні подивитися цей приклад, щоб зрозуміти архітектуру Lazy Loading

private readonly Lazy<List<int>> list = new Lazy<List<int>>(() =>
{
    List<int> configList = new List<int>(Thread.CurrentThread.ManagedThreadId);
    return configList;
});
public void Execute()
{
    list.Value.Add(0);
    if (list.IsValueCreated)
    {
        list.Value.Add(1);
        list.Value.Add(2);

        foreach (var item in list.Value)
        {
            Console.WriteLine(item);
        }
    }
    else
    {
        Console.WriteLine("Value not created");
    }
}

-> вихід -> 0 1 2

але якщо цей код не напишіть "list.Value.Add (0);"

output -> Значення не створено

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.