C # Lazy Loaded Automatische Eigenschaften


100

In C #,

Gibt es eine Möglichkeit, eine automatische Eigenschaft in eine verzögert geladene automatische Eigenschaft mit einem angegebenen Standardwert umzuwandeln?

Im Wesentlichen versuche ich dies zu ändern ...

private string _SomeVariable

public string SomeVariable
{
     get
     {
          if(_SomeVariable == null)
          {
             _SomeVariable = SomeClass.IOnlyWantToCallYouOnce();
          }

          return _SomeVariable;
     }
}

in etwas anderes, wo ich den Standard angeben kann und es den Rest automatisch erledigt ...

[SetUsing(SomeClass.IOnlyWantToCallYouOnce())]
public string SomeVariable {get; private set;}

@Gabe: Beachten Sie, dass die Klasse nur einmal aufgerufen wird, wenn sie niemals null zurückgibt.
RedFilter

Ich entdeckte, dass ... es scheint, das Singleton-Muster zu verwenden
Ctorx

Antworten:


112

Nein, da ist kein. Automatisch implementierte Eigenschaften dienen nur dazu, die grundlegendsten Eigenschaften zu implementieren: Hintergrundfeld mit Getter und Setter. Diese Art der Anpassung wird nicht unterstützt.

Sie können jedoch den Lazy<T>Typ 4.0 verwenden, um dieses Muster zu erstellen

private Lazy<string> _someVariable =new Lazy<string>(SomeClass.IOnlyWantToCallYouOnce);
public string SomeVariable => _someVariable.Value;

Dieser Code berechnet träge den Wert des _someVariableersten Aufrufs des ValueAusdrucks. Es wird nur einmal berechnet und der Wert für zukünftige Verwendungen der ValueEigenschaft zwischengespeichert


1
Eigentlich sieht es für mich so aus, als würde Lazy das Singleton-Muster implementieren. Das ist nicht mein Ziel ... mein Ziel ist es, eine faul geladene Eigenschaft zu erstellen, die träge instanziiert, aber zusammen mit der Instanz der Klasse, in der sie lebt, angeordnet ist. Lazy scheint nicht so aufzutreten.
Ctorx

19
@ctorx Lazy hat nichts mit dem Singleton-Muster zu tun. Es macht genau das, was Sie wollen.
user247702

8
Beachten Sie, dass SomeClass.IOnlyWantToCallYouOncein Ihrem Beispiel statisch sein muss, um mit einem Feldinitialisierer verwendet zu werden.
rory.ap

Tolle Antwort. In meiner Antwort finden Sie ein Visual Studio-Snippet, das Sie verwenden können, wenn Sie viele faule Eigenschaften erwarten.
Zephryl

40

Am prägnantesten ist es wahrscheinlich, den Null-Koaleszenz-Operator zu verwenden:

get { return _SomeVariable ?? (_SomeVariable = SomeClass.IOnlyWantToCallYouOnce()); }

10
In dem Fall, IOnlyWantToCallYouOncein dem zurückgegeben wird null, wird es mehrmals aufgerufen.
JaredPar

9
Bei Verwendung des Null-Koaleszenz-Operators schlägt das obige Beispiel fehl. Die korrekte Syntax lautet: _SomeVariable ?? ( _SomeVariable = SomeClass.IOnlyWantToCallYouOnce() );- Beachten Sie das Hinzufügen der Klammer um die Einstellung, _SomeVariablewenn sie null ist.
Metro Schlumpf

Dies ist die beste Option. Zuerst habe ich verwendet Lazy<>, aber für unsere Zwecke hat dies besser funktioniert. Mit dem neuesten C # kann es auch noch präziser geschrieben werden. => _SomeVariable ?? (_SomeVariable = SomeClass.IOnlyWantToCallYouOnce());Was manche auf den ersten Blick vielleicht nicht bemerken, ist, dass der Operator den rechten Operanden auswertet und sein Ergebnis zurückgibt .
RunninglVlan

15

In C # 6 gibt es eine neue Funktion namens Expression Bodied Auto-Properties , mit der Sie sie etwas sauberer schreiben können:

public class SomeClass
{ 
   private Lazy<string> _someVariable = new Lazy<string>(SomeClass.IOnlyWantToCallYouOnce);

   public string SomeVariable 
   {
      get { return _someVariable.Value; }
   }
}

Kann jetzt geschrieben werden als:

public class SomeClass
{
   private Lazy<string> _someVariable = new Lazy<string>(SomeClass.IOnlyWantToCallYouOnce);

   public string SomeVariable => _someVariable.Value;
}

Im letzten Codeabschnitt ist die Initialisierung nicht wirklich faul. IOnlyWantToCallYouOncewird während der Erstellung jedes Mal aufgerufen, wenn die Klasse instanziiert wird.
Tom Blodget

Also mit anderen Worten ist dies nicht faul geladen?
Zapnologica

@Zapnologica Meine vorherige Antwort war etwas falsch, aber ich habe sie aktualisiert. SomeVariableist faul geladen.
Alexander Derck

Diese Antwort ähnelt eher einer Tonhöhe für Expression Bodied Auto-Properties.
Little Endian

@AbleArcher Auf eine neue Sprachfunktion hinzuweisen ist jetzt eine Tonhöhe?
Alexander Derck

5

Nicht so, Parameter für Attribute müssen einen konstanten Wert haben, Sie können keinen Code aufrufen (auch keinen statischen Code).

Möglicherweise können Sie jedoch etwas mit den Aspekten von PostSharp implementieren.

Schau sie dir an:

PostSharp


5

Hier ist meine Implementierung einer Lösung für Ihr Problem. Grundsätzlich handelt es sich um eine Eigenschaft, die beim ersten Zugriff von einer Funktion festgelegt wird, und nachfolgende Zugriffe ergeben denselben Rückgabewert wie der erste.

public class LazyProperty<T>
{
    bool _initialized = false;
    T _result;

    public T Value(Func<T> fn)
    {
        if (!_initialized)
        {
            _result = fn();
            _initialized = true;
        }
        return _result;
    }
 }

Dann zu verwenden:

LazyProperty<Color> _eyeColor = new LazyProperty<Color>();
public Color EyeColor
{ 
    get 
    {
        return _eyeColor.Value(() => SomeCPUHungryMethod());
    } 
}

Es gibt natürlich den Aufwand, den Funktionszeiger herumzugeben, aber er erledigt den Job für mich und ich bemerke nicht zu viel Aufwand im Vergleich zum wiederholten Ausführen der Methode.


Wäre es nicht sinnvoller, die Funktion dem Konstruktor zu geben? Auf diese Weise würden Sie es nicht jedes Mal inline erstellen, und Sie könnten es entsorgen, nachdem Sie es zum ersten Mal verwendet haben.
Mikkel R. Lund

@ lund.mikkel ja, das würde auch funktionieren. Kann Anwendungsfälle für beide Ansätze sein.
deepee1

5
Wenn Sie die Funktion an den Konstruktor übergeben, ähnlich wie in der Lazy-Klasse von .Net, muss die übergebene Funktion statisch sein. Ich weiß, dass dies in vielen Fällen nicht zu meinem Design passt.
knusprig

@ MikkelR.Lund Manchmal möchten Sie keinen Code im Konstruktor ausführen, sondern nur bei Bedarf (und das Ergebnis in Form eines Hintergrundfelds zwischenspeichern)
mamuesstack

3

Ich bin ein großer Fan dieser Idee und möchte das folgende C # -Snippet anbieten, das ich proplazy.snippet genannt habe (Sie können es entweder importieren oder in den Standardordner einfügen, den Sie über den Snippet-Manager erhalten können).

Hier ist ein Beispiel für die Ausgabe:

private Lazy<int> myProperty = new Lazy<int>(()=>1);
public int MyProperty { get { return myProperty.Value; } }

Hier ist der Inhalt der Snippet-Datei: (als proplazy.snippet speichern)

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets  xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
    <CodeSnippet Format="1.0.0">
        <Header>
            <Title>proplazy</Title>
            <Shortcut>proplazy</Shortcut>
            <Description>Code snippet for property and backing field</Description>
            <Author>Microsoft Corporation</Author>
            <SnippetTypes>
                <SnippetType>Expansion</SnippetType>
            </SnippetTypes>
        </Header>
        <Snippet>
            <Declarations>
                <Literal>
                    <ID>type</ID>
                    <ToolTip>Property type</ToolTip>
                    <Default>int</Default>
                </Literal>
                <Literal>
                    <ID>field</ID>
                    <ToolTip>The variable backing this property</ToolTip>
                    <Default>myVar</Default>
                </Literal>
                <Literal>
                    <ID>func</ID>
                    <ToolTip>The function providing the lazy value</ToolTip>
                </Literal>
                <Literal>
                    <ID>property</ID>
                    <ToolTip>Property name</ToolTip>
                    <Default>MyProperty</Default>
                </Literal>

            </Declarations>
            <Code Language="csharp"><![CDATA[private Lazy<$type$> $field$ = new Lazy<$type$>($func$);
            public $type$ $property$ { get{ return $field$.Value; } }
            $end$]]>
            </Code>
        </Snippet>
    </CodeSnippet>
</CodeSnippets>

2

Ich denke nicht, dass dies mit reinem C # möglich ist. Sie können dies jedoch mit einem IL-Umschreiber wie PostSharp tun . Beispielsweise können Sie abhängig von den Attributen Handler vor und nach Funktionen hinzufügen.


1

Ich habe es so gemacht:

public static class LazyCachableGetter
{
    private static ConditionalWeakTable<object, IDictionary<string, object>> Instances = new ConditionalWeakTable<object, IDictionary<string, object>>();
    public static R LazyValue<T, R>(this T obj, Func<R> factory, [CallerMemberName] string prop = "")
    {
        R result = default(R);
        if (!ReferenceEquals(obj, null))
        {
            if (!Instances.TryGetValue(obj, out var cache))
            {
                cache = new ConcurrentDictionary<string, object>();
                Instances.Add(obj, cache);

            }


            if (!cache.TryGetValue(prop, out var cached))
            {
                cache[prop] = (result = factory());
            }
            else
            {
                result = (R)cached;
            }

        }
        return result;
    }
}

und später können Sie es wie verwenden

       public virtual bool SomeProperty => this.LazyValue(() =>
    {
        return true; 
    });

Wie verwende ich "dies" in diesem Kontext?
Riera

@ Riera was meinst du? Genau wie normales Eigentum. ZB public ISet<String> RegularProperty {get;set} public string CalculatedProperty => this.LazyValue(() => { return string.Join(",", RegularProperty.ToArray()); });
Alexander Zuban

0

https://github.com/bcuff/AutoLazy verwendet Fody, um Ihnen so etwas zu geben

public class MyClass
{
    // This would work as a method, e.g. GetSettings(), as well.
    [Lazy]
    public static Settings Settings
    {
        get
        {
            using (var fs = File.Open("settings.xml", FileMode.Open))
            {
                var serializer = new XmlSerializer(typeof(Settings));
                return (Settings)serializer.Deserialize(fs);
            }
        }
    }

    [Lazy]
    public static Settings GetSettingsFile(string fileName)
    {
        using (var fs = File.Open(fileName, FileMode.Open))
        {
            var serializer = new XmlSerializer(typeof(Settings));
            return (Settings)serializer.Deserialize(fs);
        }
    }
}

0
[Serializable]
public class RaporImza
{
    private readonly Func<ReportConfig> _getReportLayout;
    public RaporImza(Func<ReportConfig> getReportLayout)
    {
        _getReportLayout = getReportLayout;
    }

    private ReportConfig _getReportLayoutResult;
    public ReportConfig GetReportLayoutResult => _getReportLayoutResult ?? (_getReportLayoutResult = _getReportLayout());

    public string ImzaAtanKisiAdi => GetReportLayoutResult.ReportSignatureName;

    public string ImzaAtanKisiUnvani => GetReportLayoutResult.ReportSignatureTitle;
    public byte[] Imza => GetReportLayoutResult.ReportSignature;
}

und ich rufe wie unten

result.RaporBilgisi = new ExchangeProgramPersonAllDataModel.RaporImza(() => _reportConfigService.GetReportLayout(documentTypeId));

1
Dies könnte zwar die Frage des Autors beantworten, es fehlen jedoch einige erklärende Wörter und Links zur Dokumentation. Rohcode-Schnipsel sind ohne einige Ausdrücke nicht sehr hilfreich. Möglicherweise ist es auch sehr hilfreich , eine gute Antwort zu schreiben . Bitte bearbeiten Sie Ihre Antwort.
Gelb

0

Wenn Sie während der verzögerten Initialisierung einen Konstruktor verwenden, können auch die folgenden Erweiterungen hilfreich sein

public static partial class New
{
    public static T Lazy<T>(ref T o) where T : class, new() => o ?? (o = new T());
    public static T Lazy<T>(ref T o, params object[] args) where T : class, new() =>
            o ?? (o = (T) Activator.CreateInstance(typeof(T), args));
}

Verwendung

    private Dictionary<string, object> _cache;

    public Dictionary<string, object> Cache => New.Lazy(ref _cache);

                    /* _cache ?? (_cache = new Dictionary<string, object>()); */

Gibt es einen Vorteil, wenn Sie Ihren Helfer verwenden LazyInitializer.EnsureInitialized()? Denn LazyInitializersoweit ich das beurteilen kann, bietet es zusätzlich zu den oben genannten Funktionen eine Fehlerbehandlung sowie Synchronisierungsfunktionen. LazyInitializer-Quellcode .
Semaj1919

0

Operator ?? = ist mit C # 8.0 und höher verfügbar, sodass Sie dies jetzt noch präziser tun können:

private string _someVariable;

public string SomeVariable => _someVariable ??= SomeClass.IOnlyWantToCallYouOnce();
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.