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;}
ctorx
quelle
@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

JaredPar
quelle
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()); }
Gabe Moothart
quelle
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;
}
Alexander Derck
quelle
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

Aren
quelle
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.

deepee1
quelle
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>
Zephryl
quelle
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.

CodesInChaos
quelle
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; 
    });
Alexander Zuban
quelle
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);
        }
    }
}
Sam
quelle
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));
murat_yuceer
quelle
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>()); */
Makeman
quelle
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();
Carlos Pozos
quelle