392 Stimmen

Wann sollte ich Lazy<T> verwenden?

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

Was ist das beste Verfahren, um mit Lazy Objects die beste Leistung zu erzielen? Kann mir jemand eine praktische Anwendung in einer realen Applikation zeigen? Mit anderen Worten, wann sollte ich sie verwenden?

294voto

James Michael Hare Punkte 36545

Normalerweise verwenden Sie es, wenn Sie etwas zum ersten Mal instanziieren wollen, wenn es tatsächlich verwendet wird. Dies verzögert die Kosten für die Erstellung bis zu dem Zeitpunkt, an dem es benötigt wird, anstatt die Kosten immer zu verursachen.

In der Regel ist dies vorzuziehen, wenn das Objekt verwendet werden kann oder nicht und die Kosten für seine Erstellung nicht trivial sind.

142voto

Matthew Punkte 26817

Sie sollten versuchen, die Verwendung von Singletons zu vermeiden, aber wenn Sie es doch einmal brauchen, Lazy<T> macht die Implementierung von trägen, thread-sicheren Singletons einfach:

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

108voto

Despertar Punkte 20373

Eine große der realen Welt Ein Beispiel für den Einsatz von "Lazy Loading" sind ORMs (Object Relation Mappers) wie Entity Framework und NHibernate.

Angenommen, Sie haben eine Entität Kunde, die Eigenschaften für Name, PhoneNumber und Orders hat. Name und PhoneNumber sind normale Zeichenketten, aber Orders ist eine Navigationseigenschaft, die eine Liste aller Bestellungen zurückgibt, die der Kunde jemals getätigt hat.

Vielleicht möchten Sie alle Ihre Kunden durchgehen und sich deren Namen und Telefonnummern notieren, um sie anzurufen. Dies ist eine sehr schnelle und einfache Aufgabe, aber stellen Sie sich vor, dass jedes Mal, wenn Sie einen Kunden anlegen, automatisch eine komplexe Verknüpfung durchgeführt wird, um Tausende von Bestellungen zurückzugeben. Das Schlimmste daran ist, dass Sie die Aufträge nicht einmal verwenden werden, so dass es eine völlige Verschwendung von Ressourcen ist!

Dies ist der perfekte Ort für Lazy Loading, denn wenn die Eigenschaft Order lazy ist, werden nicht alle Kundenbestellungen abgerufen, es sei denn, Sie benötigen sie tatsächlich. Sie können die Kundenobjekte aufzählen, um nur ihren Namen und ihre Telefonnummer abzurufen, während die Eigenschaft "Order" geduldig schläft und bereit ist, wenn Sie sie brauchen.

49voto

Ben Punkte 3091

Ich habe überlegt, ob ich die Lazy<T> Eigenschaften, um die Leistung meines eigenen Codes zu verbessern (und um etwas mehr darüber zu lernen). Ich kam hier auf der Suche nach Antworten über, wenn es zu verwenden, aber es scheint, dass überall, wo ich gehe gibt es Phrasen wie:

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

von MSDN Lazy<T> Klasse

Ich bin ein wenig verwirrt, weil ich nicht sicher bin, wo ich die Grenze ziehen soll. Ich betrachte zum Beispiel die lineare Interpolation als eine ziemlich schnelle Berechnung, aber wenn ich sie nicht durchführen muss, kann ich sie dann mit Hilfe der trägen Initialisierung vermeiden und ist sie es wert?

Schließlich beschloss ich, meinen eigenen Test durchzuführen, und dachte, ich würde die Ergebnisse hier mitteilen. Leider bin ich kein wirklicher Experte für diese Art von Tests und freue mich daher über Kommentare mit Verbesserungsvorschlägen.

Beschreibung

In meinem Fall war ich besonders daran interessiert, zu sehen, ob Lazy Properties helfen könnte, einen Teil meines Codes zu verbessern, der eine Menge Interpolationen durchführt (die meisten davon sind ungenutzt), und so habe ich einen Test erstellt, der 3 Ansätze vergleicht.

Ich habe für jeden Ansatz eine eigene Testklasse mit 20 Testeigenschaften (nennen wir sie t-properties) erstellt.

  • GetInterp Klasse: Führt jedes Mal eine lineare Interpolation durch, wenn eine t-Eigenschaft erhalten wird.
  • InitInterp Klasse: Initialisiert die t-Eigenschaften, indem die lineare Interpolation für jede Eigenschaft im Konstruktor ausgeführt wird. Die Funktion get gibt einfach einen Double zurück.
  • InitLazy-Klasse: Richtet die t-Eigenschaften als Lazy-Eigenschaften ein, so dass die lineare Interpolation einmal ausgeführt wird, wenn die Eigenschaft zum ersten Mal abgerufen wird. Spätere Abfragen sollten einfach einen bereits berechneten Double zurückgeben.

Die Testergebnisse werden in ms gemessen und sind der Durchschnitt von 50 Instanziierungen oder 20 Property Gets. 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 (Durchschnitt von 20 Property Gets)

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: Second Get (Durchschnitt von 20 Property Gets)

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

GetInterp ist erwartungsgemäß am schnellsten zu instanziieren, da es nichts tut. InitLazy ist schneller zu instanziieren als InitInterp was darauf hindeutet, dass der Overhead beim Einrichten von "Lazy Properties" schneller ist als meine lineare Interpolationsberechnung. Allerdings bin ich hier ein wenig verwirrt, weil InitInterp sollte 20 lineare Interpolationen durchführen (um seine t-Eigenschaften einzurichten), benötigt aber nur 0,09 ms zum Instanziieren (Test 1), verglichen mit GetInterp was beim ersten Mal (Test 2) 0,28 ms und beim zweiten Mal (Test 3) 0,1 ms für eine einzige lineare Interpolation benötigt.

Es braucht InitLazy fast 2 mal länger als GetInterp um eine Eigenschaft beim ersten Mal zu erhalten, während InitInterp ist die schnellste, weil sie ihre Eigenschaften während der Instanziierung auffüllt. (Zumindest sollte es das tun, aber warum war das Ergebnis der Instanziierung so viel schneller als eine einfache lineare Interpolation? Wann genau führt es diese Interpolationen durch?)

Leider sieht es so aus, als ob in meinen Tests eine automatische Code-Optimierung durchgeführt wird. Es sollte dauern GetInterp die gleiche Zeit, um eine Eigenschaft beim ersten Mal zu erhalten, wie beim zweiten Mal, aber es wird als mehr als 2x schneller angezeigt. Es sieht so aus, als ob sich diese Optimierung auch auf die anderen Klassen auswirkt, da sie alle etwa die gleiche Zeit für Test 3 benötigen. Solche Optimierungen können jedoch auch in meinem eigenen Produktionscode stattfinden, was ebenfalls ein wichtiger Aspekt sein kann.

Schlussfolgerungen

Während einige Ergebnisse den Erwartungen entsprechen, gibt es auch einige sehr interessante unerwartete Ergebnisse, die wahrscheinlich auf Code-Optimierungen zurückzuführen sind. Selbst bei Klassen, die im Konstruktor viel Arbeit zu machen scheinen, zeigen die Ergebnisse der Instanziierung, dass sie im Vergleich zum Erhalt einer doppelten Eigenschaft sehr schnell erstellt werden können. Während Experten auf diesem Gebiet vielleicht in der Lage sind, sich dazu zu äußern und die Sache genauer zu untersuchen, habe ich persönlich das Gefühl, dass ich diesen Test noch einmal durchführen muss, allerdings mit meinem Produktionscode, um zu untersuchen, welche Art von Optimierungen auch dort stattfinden könnten. Ich erwarte jedoch, dass InitInterp könnte der richtige Weg sein.

22voto

Ich möchte das von Matthew gepostete Beispiel nur ergänzen:

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 Lazy Teil des Frameworks war, 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 ;
}

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X