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.