Proprietà automatiche con caricamento lento C #


100

In C #,

C'è un modo per trasformare una proprietà automatica in una proprietà automatica caricata in modo pigro con un valore predefinito specificato?

In sostanza, sto cercando di trasformare questo ...

private string _SomeVariable

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

          return _SomeVariable;
     }
}

in qualcosa di diverso, dove posso specificare il valore predefinito e gestisce il resto automaticamente ...

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

@Gabe: Nota che la classe verrà chiamata solo una volta se non restituisce mai null.
RedFilter

Ho scoperto che ... sembra che utilizzi il pattern singleton
ctorx

Risposte:


112

No non c'è. Le proprietà implementate automaticamente funzionano solo per implementare le proprietà più elementari: campo di supporto con getter e setter. Non supporta questo tipo di personalizzazione.

Tuttavia è possibile utilizzare il Lazy<T>tipo 4.0 per creare questo modello

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

Questo codice calcolerà pigramente il valore della _someVariableprima volta che l' Valueespressione viene chiamata. Verrà calcolato solo una volta e memorizzerà il valore nella cache per usi futuri della Valueproprietà


1
In realtà, mi sembra che Lazy implementa il pattern singleton. Questo non è il mio obiettivo ... il mio obiettivo è creare una proprietà caricata in modo pigro che sia istanziata pigramente ma disposta insieme all'istanza della classe in cui vive. Lazy non sembra comportarsi in quel modo.
ctorx

19
@ctorx Lazy non ha nulla a che fare con il pattern singleton. Fa esattamente quello che vuoi che faccia.
user247702

8
Nota, SomeClass.IOnlyWantToCallYouOncenel tuo esempio deve essere statico per essere utilizzato con un inizializzatore di campo.
rory.ap

Risposta fantastica. Vedi la mia risposta per uno snippet di Visual Studio che puoi usare se prevedi di avere molte proprietà pigre.
Zephryl

40

Probabilmente il più conciso che puoi ottenere è usare l'operatore di coalescenza nullo:

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

10
Nel caso IOnlyWantToCallYouOnceritorni nulllo chiamerà più di una volta.
JaredPar

9
Quando si utilizza l'operatore di coalescenza null, l'esempio precedente avrà esito negativo. La sintassi corretta è: _SomeVariable ?? ( _SomeVariable = SomeClass.IOnlyWantToCallYouOnce() );- notare l'aggiunta della parentesi attorno all'impostazione _SomeVariablese è nulla.
Metro Smurf

Questa è l'opzione migliore. Prima ho usato Lazy<>, ma per i nostri scopi ha funzionato meglio. Con l'ultimo C # può anche essere scritto in modo ancora più conciso => _SomeVariable ?? (_SomeVariable = SomeClass.IOnlyWantToCallYouOnce());Ciò che alcuni potrebbero non notare dal primo sguardo è che l'operatore valuta l'operando di destra e restituisce il suo risultato .
RunninglVlan il

15

C'è una nuova funzionalità in C # 6 chiamata Expression Bodied Auto-Properties , che ti consente di scriverlo in modo un po 'più pulito:

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

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

Ora può essere scritto come:

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

   public string SomeVariable => _someVariable.Value;
}

Nell'ultima sezione di codice, l'inizializzazione non è effettivamente pigra. IOnlyWantToCallYouOnceverrebbe chiamato durante la costruzione ogni volta che viene istanziata la classe.
Tom Blodget

Quindi in altre parole questo non è caricato pigro?
Zapnologica

@Zapnologica La mia risposta precedente era un po 'sbagliata ma l'ho aggiornata. SomeVariableè pigro caricato.
Alexander Derck

Questa risposta sembra più un passo per le proprietà automatiche di Expression Bodied.
Little Endian

@AbleArcher Stai sottolineando che una nuova funzione linguistica è una presentazione adesso?
Alexander Derck

5

Non così, i parametri per gli attributi devono essere costanti nel valore, non è possibile chiamare codice (anche codice statico).

Potresti comunque essere in grado di implementare qualcosa con gli aspetti di PostSharp.

Controllali:

PostSharp


5

Ecco la mia implementazione di una soluzione al tuo problema. Fondamentalmente l'idea è una proprietà che verrà impostata da una funzione al primo accesso e gli accessi successivi produrranno lo stesso valore di ritorno del primo.

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

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

Quindi utilizzare:

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

Ovviamente c'è l'overhead di passare il puntatore alla funzione, ma fa il lavoro per me e non noto troppo overhead rispetto all'esecuzione del metodo più e più volte.


Non avrebbe più senso dare la funzione al costruttore? In questo modo non lo creeresti in linea ogni volta e potresti smaltirlo dopo averlo usato la prima volta.
Mikkel R. Lund

@ lund.mikkel sì, funzionerebbe anche questo. Possono essere casi d'uso per entrambi gli approcci.
deepee1

5
Se passi la funzione al costruttore, proprio come la classe Lazy di .Net, la funzione passata dovrà essere statica, so che in molti casi non si adatta al mio progetto.
croccante

@ MikkelR.Lund A volte non vuoi eseguire del codice nel costruttore ma solo su richiesta (e memorizzare nella cache il risultato sotto forma di campo di supporto)
mamuesstack

3

Sono un grande fan di questa idea e vorrei offrire il seguente frammento C # che ho chiamato proplazy.snippet. (Puoi importarlo o incollarlo nella cartella standard che puoi ottenere da Snippet Manager)

Ecco un esempio del suo output:

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

Ecco il contenuto del file snippet: (salva come proplazy.snippet)

<?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

Non penso che questo sia possibile con il C # puro. Ma potresti farlo usando un rewriter IL come PostSharp . Ad esempio, consente di aggiungere gestori prima e dopo le funzioni a seconda degli attributi.


1

L'ho fatto così:

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

e successivamente puoi usarlo come

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

Come si usa "this" in questo contesto?
Riera

@Riera cosa intendi? Proprio come una proprietà normale. Ad esempio public ISet<String> RegularProperty {get;set} public string CalculatedProperty => this.LazyValue(() => { return string.Join(",", RegularProperty.ToArray()); });
Alexander Zuban,

1

L'operatore ?? = è disponibile utilizzando C # 8.0 e versioni successive, quindi ora puoi farlo in modo ancora più conciso:

private string _someVariable;

public string SomeVariable => _someVariable ??= SomeClass.IOnlyWantToCallYouOnce();

0

https://github.com/bcuff/AutoLazy usa Fody per darti qualcosa di simile

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

e io chiamo come muggito

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

1
Anche se questo potrebbe rispondere alla domanda degli autori, mancano alcune parole esplicative e collegamenti alla documentazione. I frammenti di codice non elaborati non sono molto utili senza alcune frasi intorno ad esso. Potresti anche trovare molto utile come scrivere una buona risposta . Modifica la tua risposta.
lunedì

0

Se si utilizza un costruttore durante l'inizializzazione lenta, potrebbero essere utili anche le seguenti estensioni

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

Utilizzo

    private Dictionary<string, object> _cache;

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

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

C'è un vantaggio nell'usare il tuo aiutante LazyInitializer.EnsureInitialized()? Perché da quello che posso dire, oltre alle funzionalità di cui sopra, LazyInitializerfornisce la gestione degli errori e la funzionalità di sincronizzazione. Codice sorgente LazyInitializer .
semaj1919
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.