Wann sollte ich Lazy <T> verwenden?

327

Ich fand diesen Artikel über Lazy: Faulheit in C # 4.0 - Faul

Was ist die beste Vorgehensweise, um mit Lazy-Objekten die beste Leistung zu erzielen? Kann mich jemand auf eine praktische Anwendung in einer realen Anwendung hinweisen? Mit anderen Worten, wann sollte ich es verwenden?

Danyolgiax
quelle
42
Ersetzt : get { if (foo == null) foo = new Foo(); return foo; }. Und es gibt zig mögliche Orte, an denen man es benutzen kann ...
Kirk Woll
57
Beachten Sie, dass dies get { if (foo == null) foo = new Foo(); return foo; }nicht threadsicher ist, während Lazy<T>es standardmäßig threadsicher ist.
Matthew
23
Von MSDN: WICHTIG: Die verzögerte Initialisierung ist threadsicher, schützt das Objekt jedoch nach der Erstellung nicht. Sie müssen das Objekt sperren, bevor Sie darauf zugreifen können, es sei denn, der Typ ist threadsicher.
Pedro.The.Kid

Antworten:

237

Sie verwenden es normalerweise, wenn Sie etwas instanziieren möchten, wenn es zum ersten Mal tatsächlich verwendet wird. Dies verzögert die Kosten für die Erstellung, bis sie benötigt werden, anstatt immer die Kosten zu verursachen.

Normalerweise ist dies vorzuziehen, wenn das Objekt verwendet werden kann oder nicht und die Kosten für dessen Konstruktion nicht trivial sind.

James Michael Hare
quelle
121
warum nicht einfach IMMER Lazy benutzen?
TruthOf42
44
Dies verursacht die Kosten bei der ersten Verwendung und kann dazu einen gewissen Sperraufwand erfordern (oder die Gewindesicherheit beeinträchtigen, wenn dies nicht der Fall ist). Daher sollte es sorgfältig ausgewählt und nur verwendet werden, wenn dies erforderlich ist.
James Michael Hare
3
James, könnten Sie bitte näher darauf eingehen, "und die Kosten für den Bau sind nicht trivial"? In meinem Fall habe ich 19 Immobilien in meiner Klasse und in den meisten Fällen müssen nur 2 oder 3 betrachtet werden. Daher denke ich darüber nach, jede Eigenschaft mit zu implementieren Lazy<T>. Um jedoch jede Eigenschaft zu erstellen, führe ich eine lineare Interpolation (oder eine bilineare Interpolation) durch, die ziemlich trivial ist, aber einige Kosten verursacht. (Wollen Sie vorschlagen, dass ich mein eigenes Experiment mache?)
Ben
3
James, meinen eigenen Rat befolgend, machte ich mein eigenes Experiment. Siehe meinen Beitrag .
Ben
17
Möglicherweise möchten Sie alles "während" des Systemstarts initialisieren / instanziieren, um die Benutzerlatenz in Systemen mit hohem Durchsatz und geringer Latenz zu verhindern. Dies ist nur einer der vielen Gründe, Lazy nicht "immer" zu verwenden.
Derrick
126

Sie sollten versuchen, die Verwendung von Singletons zu vermeiden. Wenn Sie dies jedoch jemals tun müssen, können Sie Lazy<T>faule, threadsichere Singletons einfach implementieren:

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;
}
Matthew
quelle
38
Ich hasse es zu lesen. Du solltest versuchen, Singletons zu vermeiden, wenn ich sie benutze: D ... jetzt muss ich lernen, warum ich versuchen sollte, sie zu vermeiden: D
Bart Calixto
24
Ich werde Singletons nicht mehr verwenden, wenn Microsoft sie in ihren Beispielen nicht mehr verwendet.
eaglei22
4
Ich neige dazu, der Vorstellung nicht zuzustimmen, Singletons meiden zu müssen. Wenn Sie dem Paradigma der Abhängigkeitsinjektion folgen, sollte es keine Rolle spielen. Im Idealfall sollten alle Ihre Abhängigkeiten nur einmal erstellt werden. Dies reduziert den Druck auf den GC in Szenarien mit hoher Last. Daher ist es in Ordnung, sie innerhalb der Klasse selbst zu einem Singleton zu machen. Die meisten (wenn nicht alle) modernen DI-Container können so oder so damit umgehen, wie Sie es möchten.
Lee Grissom
1
Sie müssen kein solches Singleton-Muster verwenden, sondern einen beliebigen di-Container verwenden, um Ihre Klasse für Singleton zu konfigurieren. Der Container übernimmt für Sie den Overhead.
VivekDev
Alles hat einen Zweck, es gibt Situationen, in denen Singletons ein guter Ansatz sind, und Situationen, in denen dies nicht der Fall ist :).
Hawkzey
86

Ein großes reale Welt Beispiel, wo ein träges Laden praktisch ist , ist mit ORM (Object Relation Mapper) wie Entity Framework und NHibernate.

Angenommen, Sie haben einen Entitätskunden mit Eigenschaften für Name, Telefonnummer und Bestellungen. Name und Telefonnummer sind reguläre Zeichenfolgen, aber Bestellungen ist eine Navigationseigenschaft, die eine Liste aller Bestellungen zurückgibt, die der Kunde jemals getätigt hat.

Möglicherweise möchten Sie häufig alle Ihre Kunden durchgehen und deren Namen und Telefonnummer abrufen, um sie anzurufen. Dies ist eine sehr schnelle und einfache Aufgabe. Stellen Sie sich jedoch vor, dass jedes Mal, wenn Sie einen Kunden erstellt haben, automatisch ein komplexer Join ausgeführt wurde, um Tausende von Bestellungen zurückzugeben. Das Schlimmste ist, dass Sie die Bestellungen nicht einmal verwenden werden, was eine völlige Verschwendung von Ressourcen darstellt!

Dies ist der perfekte Ort für das verzögerte Laden, denn wenn die Order-Eigenschaft faul ist, werden nicht alle Bestellungen des Kunden abgerufen, es sei denn, Sie benötigen sie tatsächlich. Sie können die Kundenobjekte auflisten, die nur ihren Namen und ihre Telefonnummer erhalten, während die Order-Eigenschaft geduldig schläft und bereit ist, wenn Sie sie benötigen.

Despertar
quelle
34
Schlechtes Beispiel, da ein solches verzögertes Laden normalerweise bereits in das ORM eingebaut ist. Sie sollten nicht damit beginnen, Ihren POCOs Lazy <T> -Werte hinzuzufügen, um ein verzögertes Laden zu erhalten, sondern die ORM-spezifische Methode verwenden, um dies zu tun.
Dynalon
56
@Dyna Dieses Beispiel bezieht sich auf das integrierte verzögerte Laden eines ORM, da dies meiner Meinung nach die Nützlichkeit des verzögerten Ladens auf klare und einfache Weise veranschaulicht.
Despertar
Wenn Sie also Entity Framework verwenden, sollte man dann seine eigene Faulheit durchsetzen? Oder macht EF das für Sie?
Zapnologica
7
@Zapnologica EF erledigt dies standardmäßig für Sie. Wenn Sie eifrig laden möchten (das Gegenteil von verzögertem Laden), müssen Sie EF explizit mit verwenden Db.Customers.Include("Orders"). Dies führt dazu, dass der Order-Join zu diesem Zeitpunkt ausgeführt wird und nicht, wenn die Customer.OrdersEigenschaft zum ersten Mal verwendet wird. Lazy Loading kann auch über den DbContext deaktiviert werden.
Despertar
2
Eigentlich ist dies ein gutes Beispiel, da man diese Funktionalität vielleicht hinzufügen möchte, wenn man so etwas wie Dapper verwendet.
Knochen
41

Ich habe überlegt, Lazy<T>Eigenschaften zu verwenden, um die Leistung meines eigenen Codes zu verbessern (und etwas mehr darüber zu erfahren). Ich bin hierher gekommen, um nach Antworten zu suchen, wann ich es verwenden soll, aber es scheint, dass überall, wo ich hingehe, Sätze wie:

Verwenden Sie die verzögerte Initialisierung, um die Erstellung eines großen oder ressourcenintensiven Objekts oder die Ausführung einer ressourcenintensiven Aufgabe zu verschieben, insbesondere wenn eine solche Erstellung oder Ausführung während der Lebensdauer des Programms möglicherweise nicht erfolgt.

aus der MSDN Lazy <T> -Klasse

Ich bin etwas verwirrt, weil ich nicht sicher bin, wo ich die Grenze ziehen soll. Zum Beispiel betrachte ich lineare Interpolation als eine ziemlich schnelle Berechnung, aber wenn ich es nicht tun muss, kann mir eine verzögerte Initialisierung helfen, dies zu vermeiden, und lohnt es sich?

Am Ende entschied ich mich, meinen eigenen Test zu versuchen und dachte, ich würde die Ergebnisse hier teilen. Leider bin ich kein Experte für diese Art von Tests und freue mich über Kommentare, die Verbesserungen vorschlagen.

Beschreibung

Für meinen Fall war ich besonders interessiert zu sehen, ob Lazy Properties dazu beitragen kann, einen Teil meines Codes zu verbessern, der viel interpoliert (der größte Teil davon wird nicht verwendet), und deshalb habe ich einen Test erstellt, der drei Ansätze vergleicht.

Ich habe für jeden Ansatz eine separate Testklasse mit 20 Testeigenschaften (nennen wir sie T-Eigenschaften) erstellt.

  • GetInterp-Klasse: Führt jedes Mal eine lineare Interpolation aus, wenn eine t-Eigenschaft abgerufen wird.
  • InitInterp-Klasse: Initialisiert die t-Eigenschaften, indem die lineare Interpolation für jede einzelne im Konstruktor ausgeführt wird. Das Get gibt nur ein Double zurück.
  • InitLazy-Klasse: Richtet die t-Eigenschaften als Lazy-Eigenschaften ein, sodass die lineare Interpolation einmal ausgeführt wird, wenn die Eigenschaft zum ersten Mal abgerufen wird. Nachfolgende Gets sollten nur ein bereits berechnetes Double zurückgeben.

Die Testergebnisse werden in ms gemessen und sind der Durchschnitt von 50 Instanziierungen oder 20 Eigenschaften. Jeder Test wurde dann 5 Mal durchgeführt.

Test 1 Ergebnisse: Instanziierung (Durchschnitt von 50 Instanziierungen)

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

Test 2 Ergebnisse: First Get (durchschnittlich 20 Property Get)

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

Test 3 Ergebnisse: Zweiter Get (Durchschnitt von 20 Property Get)

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

Beobachtungen

GetInterpist am schnellsten wie erwartet zu instanziieren, da es nichts tut. InitLazyist schneller zu instanziieren als zu InitInterpvermuten, dass der Overhead beim Einrichten von Lazy-Eigenschaften schneller ist als meine lineare Interpolationsberechnung. Ich bin hier jedoch etwas verwirrt, da InitInterp20 lineare Interpolationen durchgeführt werden sollten (um die t-Eigenschaften festzulegen), GetInterpdie Instanziierung jedoch nur 0,09 ms dauert (Test 1), verglichen mit 0,28 ms, um nur eine lineare Interpolation durchzuführen das erste Mal (Test 2) und 0,1 ms, um es das zweite Mal zu tun (Test 3).

Es dauert InitLazyfast zweimal länger als GetInterpbeim ersten Abrufen einer Eigenschaft, während InitInterpes am schnellsten ist, da es seine Eigenschaften während der Instanziierung auffüllt. (Zumindest hätte es das tun sollen, aber warum war das Instanziierungsergebnis so viel schneller als eine einzelne lineare Interpolation? Wann genau werden diese Interpolationen durchgeführt?)

Leider sieht es so aus, als ob in meinen Tests eine automatische Codeoptimierung stattfindet. Es sollte GetInterpgenauso lange dauern, bis eine Eigenschaft beim ersten Mal abgerufen wird wie beim zweiten Mal, aber sie wird mehr als zweimal schneller angezeigt. Es sieht so aus, als ob diese Optimierung auch die anderen Klassen betrifft, da sie alle ungefähr die gleiche Zeit für Test 3 benötigen. Solche Optimierungen können jedoch auch in meinem eigenen Produktionscode stattfinden, was ebenfalls eine wichtige Überlegung sein kann.

Schlussfolgerungen

Während einige Ergebnisse wie erwartet sind, gibt es auch einige sehr interessante unerwartete Ergebnisse, wahrscheinlich aufgrund von Codeoptimierungen. Selbst für Klassen, die so aussehen, als würden sie viel im Konstruktor arbeiten, zeigen die Instanziierungsergebnisse, dass sie im Vergleich zum Abrufen einer doppelten Eigenschaft möglicherweise immer noch sehr schnell erstellt werden können. Während Experten auf diesem Gebiet möglicherweise in der Lage sind, Kommentare abzugeben und eingehender zu untersuchen, ist mein persönliches Gefühl, dass ich diesen Test erneut durchführen muss, jedoch anhand meines Produktionscodes, um zu untersuchen, welche Art von Optimierungen auch dort stattfinden können. Ich InitInterpgehe jedoch davon aus, dass dies der richtige Weg sein wird.

Ben
quelle
26
Vielleicht sollten Sie Ihren
Testcode
1
Ich glaube, der Hauptkompromiss besteht zwischen der Speichernutzung (faul) und der CPU-Nutzung (nicht faul). Da lazyeinige zusätzliche Buchhaltung erforderlich ist, InitLazywürde mehr Speicher als die anderen Lösungen verbrauchen. Bei jedem Zugriff kann es auch zu geringfügigen Leistungseinbußen kommen, während geprüft wird, ob bereits ein Wert vorhanden ist oder nicht. clevere Tricks könnten diesen Aufwand beseitigen, aber es würde besondere Unterstützung in IL erfordern. (Haskell macht dies, indem er jeden faulen Wert zu einem Funktionsaufruf macht; sobald der Wert generiert ist, wird er durch eine Funktion ersetzt, die diesen Wert jedes Mal zurückgibt.)
jpaugh
14

Nur um auf das Beispiel von Mathew zu verweisen

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; }
    }
}

Bevor der Faule geboren wurde, hätten wir es so gemacht:

private static object lockingObject = new object();
public static LazySample InstanceCreation()
{
    if(lazilyInitObject == null)
    {
         lock (lockingObject)
         {
              if(lazilyInitObject == null)
              {
                   lazilyInitObject = new LazySample ();
              }
         }
    }
    return lazilyInitObject ;
}
Thulani Chivandikwa
quelle
6
Ich benutze dafür immer einen IoC-Container.
Jowen
1
Ich stimme voll und ganz zu, einen IoC-Container dafür in Betracht zu ziehen. Wenn Sie jedoch ein einfaches, faul initialisiertes Objekt-Singleton möchten, sollten Sie auch berücksichtigen, dass dies, wenn Sie dies nicht benötigen, um threadsicher zu sein, manuell mit einem If durchgeführt werden kann. Dies ist möglicherweise am besten, wenn Sie den Leistungsaufwand für den Umgang von Lazy selbst berücksichtigen.
Thulani Chivandikwa
12

Von MSDN:

Verwenden Sie eine Instanz von Lazy, um die Erstellung eines großen oder ressourcenintensiven Objekts oder die Ausführung einer ressourcenintensiven Aufgabe zu verschieben, insbesondere wenn eine solche Erstellung oder Ausführung möglicherweise nicht während der Lebensdauer des Programms erfolgt.

Zusätzlich zur Antwort von James Michael Hare bietet Lazy eine thread-sichere Initialisierung Ihres Werts. Werfen Sie einen Blick auf LazyThreadSafetyMode Aufzählung MSDN Eintrag beschreibt verschiedene Arten von Thread - Sicherheit Modi für diese Klasse.

Vasea
quelle
-2

Sie sollten sich dieses Beispiel ansehen, um die Lazy Loading-Architektur zu verstehen

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");
    }
}

-> Ausgabe -> 0 1 2

aber wenn dieser Code nicht schreibt "list.Value.Add (0);"

Ausgabe -> Wert nicht erstellt

Tufy Duck
quelle