Propriétés automatiques à chargement différé C #

100

En C #,

Existe-t-il un moyen de transformer une propriété automatique en propriété automatique chargée différemment avec une valeur par défaut spécifiée?

Essentiellement, j'essaie de transformer cela ...

private string _SomeVariable

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

          return _SomeVariable;
     }
}

en quelque chose de différent, où je peux spécifier la valeur par défaut et il gère le reste automatiquement ...

[SetUsing(SomeClass.IOnlyWantToCallYouOnce())]
public string SomeVariable {get; private set;}
ctorx
la source
@Gabe: Notez que la classe ne sera appelée qu'une seule fois si elle ne renvoie jamais null.
RedFilter
J'ai découvert que ... il semble qu'il utilise le modèle singleton
ctorx

Réponses:

112

Non, il n'y en a pas. Les propriétés implémentées automatiquement ne fonctionnent que pour implémenter les propriétés les plus élémentaires: un champ de sauvegarde avec getter et setter. Il ne prend pas en charge ce type de personnalisation.

Cependant, vous pouvez utiliser le Lazy<T>type 4.0 pour créer ce modèle

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

Ce code calculera paresseusement la valeur de _someVariablela première fois que l' Valueexpression est appelée. Il ne sera calculé qu'une seule fois et mettra en cache la valeur pour les utilisations futures de la Valuepropriété

JaredPar
la source
1
En fait, il me semble que Lazy implémente le modèle singleton. Ce n'est pas mon objectif ... mon objectif est de créer une propriété chargée paresseusement qui est instanciée paresseusement mais disposée avec l'instance de la classe dans laquelle elle vit. Lazy ne semble pas fonctionner de cette façon.
ctorx
19
@ctorx Lazy n'a rien à voir avec le modèle singleton. Il fait exactement ce que vous voulez qu'il fasse.
user247702
8
Notez, SomeClass.IOnlyWantToCallYouOncedans votre exemple, doit être statique pour être utilisé avec un initialiseur de champ.
rory.ap
Réponse géniale. Voir ma réponse pour un extrait de code Visual Studio que vous pouvez utiliser si vous prévoyez d'avoir de nombreuses propriétés paresseuses.
Zephryl
40

Le plus concis que vous puissiez obtenir est probablement d'utiliser l'opérateur null-coalescing:

get { return _SomeVariable ?? (_SomeVariable = SomeClass.IOnlyWantToCallYouOnce()); }
Gabe Moothart
la source
10
Dans le cas de IOnlyWantToCallYouOnceretour, nullil l'appellera plus d'une fois.
JaredPar
9
Lors de l'utilisation de l'opérateur de fusion nul, l'exemple ci-dessus échouera. La syntaxe correcte est: _SomeVariable ?? ( _SomeVariable = SomeClass.IOnlyWantToCallYouOnce() );- notez l'ajout de parenthèses autour du paramètre _SomeVariables'il est nul.
Metro Schtroumpf
C'est la meilleure solution. J'ai d'abord utilisé Lazy<>, mais pour nos besoins, cela fonctionnait mieux. Avec le dernier C #, il peut également être écrit encore plus concis. => _SomeVariable ?? (_SomeVariable = SomeClass.IOnlyWantToCallYouOnce());Ce que certains pourraient ne pas remarquer du premier coup d'œil, c'est que l'opérateur évalue l'opérande de droite et renvoie son résultat .
RunninglVlan
15

Il y a une nouvelle fonctionnalité en C # 6 appelée Expression Bodied Auto-Properties , qui vous permet de l'écrire un peu plus clairement:

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

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

Peut maintenant être écrit comme:

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

   public string SomeVariable => _someVariable.Value;
}
Alexandre Derck
la source
Dans la dernière section de code, l'initialisation n'est pas réellement paresseuse. IOnlyWantToCallYouOnceserait appelée pendant la construction à chaque fois que la classe est instanciée.
Tom Blodget
Donc, en d'autres termes, ce n'est pas chargé paresseux?
Zapnologica
@Zapnologica Ma réponse précédente était un peu fausse mais je l'ai mise à jour. SomeVariableest chargé paresseusement.
Alexander Derck
Cette réponse ressemble plus à un argumentaire pour les propriétés automatiques à corps d'expression.
Little Endian
@AbleArcher Soulignant qu'une nouvelle fonctionnalité linguistique est un argumentaire maintenant?
Alexander Derck du
5

Pas comme ça, les paramètres des attributs doivent être de valeur constante, vous ne pouvez pas appeler de code (même code statique).

Vous pouvez cependant être en mesure d'implémenter quelque chose avec les aspects de PostSharp.

Vérifie-les:

PostSharp

Aren
la source
5

Voici ma mise en œuvre d'une solution à votre problème. Fondamentalement, l'idée est une propriété qui sera définie par une fonction au premier accès et les accès suivants produiront la même valeur de retour que le premier.

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

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

Puis utiliser:

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

Il y a bien sûr la surcharge de passer le pointeur de fonction, mais cela fait le travail pour moi et je ne remarque pas trop de surcharge par rapport à l'exécution de la méthode encore et encore.

deepee1
la source
Ne serait-il pas plus logique de donner la fonction au constructeur? De cette façon, vous ne le créeriez pas en ligne à chaque fois et vous pourriez le supprimer après l'avoir utilisé la première fois.
Mikkel R. Lund
@ lund.mikkel ouais, cela fonctionnerait aussi. Peut être des cas d'utilisation pour les deux approches.
deepee1
5
Si vous passez la fonction au constructeur, un peu comme la classe Lazy de .Net, alors la fonction transmise devra être statique, je sais que cela ne correspond pas à ma conception dans de nombreux cas.
croquant
@ MikkelR.Lund Parfois, vous ne voulez pas exécuter du code dans le constructeur mais uniquement à la demande (et mettre en cache le résultat sous la forme d'un champ de sauvegarde)
mamuesstack
3

Je suis un grand fan de cette idée et j'aimerais vous proposer l'extrait de code C # suivant que j'ai appelé proplazy.snippet (vous pouvez soit l'importer, soit le coller dans le dossier standard que vous pouvez obtenir à partir du gestionnaire d'extraits de code)

Voici un exemple de sa sortie:

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

Voici le contenu du fichier d'extrait: (enregistrer sous 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>
Zephryl
la source
2

Je ne pense pas que cela soit possible avec du C # pur. Mais vous pouvez le faire en utilisant un réécriveur IL comme PostSharp . Par exemple, il vous permet d'ajouter des gestionnaires avant et après des fonctions en fonction des attributs.

CodesInChaos
la source
2

Operator ?? = est disponible en utilisant C # 8.0 et versions ultérieures, vous pouvez donc le faire encore plus concis:

private string _someVariable;

public string SomeVariable => _someVariable ??= SomeClass.IOnlyWantToCallYouOnce();
Carlos Pozos
la source
1

Je l'ai fait comme ça:

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

et plus tard, vous pouvez l'utiliser comme

       public virtual bool SomeProperty => this.LazyValue(() =>
    {
        return true; 
    });
Alexandre Zuban
la source
Comment utiliser «ceci» dans ce contexte?
Riera
@Riera que voulez-vous dire? Tout comme la propriété ordinaire. Par exemple public ISet<String> RegularProperty {get;set} public string CalculatedProperty => this.LazyValue(() => { return string.Join(",", RegularProperty.ToArray()); });
Alexander Zuban
0

https://github.com/bcuff/AutoLazy utilise Fody pour vous donner quelque chose comme ça

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

et j'appelle comme ci-dessous

result.RaporBilgisi = new ExchangeProgramPersonAllDataModel.RaporImza(() => _reportConfigService.GetReportLayout(documentTypeId));
murat_yuceer
la source
1
Bien que cela puisse répondre à la question des auteurs, il manque des mots explicatifs et des liens vers la documentation. Les extraits de code bruts ne sont pas très utiles sans quelques phrases autour. Vous pouvez également trouver comment rédiger une bonne réponse très utile. Veuillez modifier votre réponse.
hellow le
0

Si vous utilisez un constructeur lors de l'initialisation tardive, les extensions suivantes peuvent également être utiles

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

Usage

    private Dictionary<string, object> _cache;

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

                    /* _cache ?? (_cache = new Dictionary<string, object>()); */
Makeman
la source
1
Y a-t-il un avantage à utiliser votre assistant LazyInitializer.EnsureInitialized()? Parce que d'après ce que je peux dire, en plus des fonctionnalités ci-dessus, LazyInitializerfournit une gestion des erreurs ainsi qu'une fonctionnalité de synchronisation. Code source de LazyInitializer .
semaj1919