En C #,
Existe-t-il un moyen de transformer une propriété automatique en propriété automatique chargée paresseuse avec une valeur par défaut spécifiée?
Essentiellement, j'essaie de transformer ceci ...
private string _SomeVariable
public string SomeVariable
{
get
{
if(_SomeVariable == null)
{
_SomeVariable = SomeClass.IOnlyWantToCallYouOnce();
}
return _SomeVariable;
}
}
dans quelque chose de différent, où je peux spécifier la valeur par défaut et gérer le reste automatiquement ...
[SetUsing(SomeClass.IOnlyWantToCallYouOnce())]
public string SomeVariable {get; private set;}
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: champ de sauvegarde avec getter et setter. Il ne supporte pas ce type de personnalisation.
Cependant, vous pouvez utiliser le type 4.0 Lazy<T>
pour créer ce modèle.
private Lazy<string> _someVariable =new Lazy<string>(SomeClass.IOnlyWantToCallYouOnce);
public string SomeVariable => _someVariable.Value;
Ce code calculera par la suite la valeur de _someVariable
lors du premier appel de l'expression Value
. Elle ne sera calculée qu'une fois et mettra en cache la valeur pour les utilisations futures de la propriété Value
Le plus concis possible est probablement d’utiliser l’opérateur null-coalescing:
get { return _SomeVariable ?? (_SomeVariable = SomeClass.IOnlyWantToCallYouOnce()); }
Il existe une nouvelle fonctionnalité dans 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;
}
Pas comme ça, les paramètres pour les attributs doivent avoir une valeur constante, vous ne pouvez pas appeler du code (même du code statique).
Cependant, vous pourrez peut-être implémenter quelque chose avec Aspects de PostSharp.
Vérifie-les:
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 lors du 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 le temps système nécessaire pour faire passer le pointeur de fonction, mais cela me convient et je ne remarque pas trop de temps système par rapport à l'exécution répétée de la méthode.
Je suis un grand fan de cette idée et voudrais proposer l'extrait C # suivant que j'ai appelé proplazy.snippet (vous pouvez l'importer ou le coller dans le dossier standard que vous pouvez obtenir à partir du Gestionnaire d'extraits)
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: (enregistrez 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>
Je ne pense pas que cela soit possible avec du C # pur. Mais vous pouvez le faire en utilisant un enregistreur IL comme PostSharp . Par exemple, cela vous permet d'ajouter des gestionnaires avant et après des fonctions en fonction d'attributs.
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;
});
[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));
Si vous utilisez un constructeur lors de l'initialisation différée, 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>()); */
https://github.com/bcuff/AutoLazy utilise Fody pour vous donner un résultat similaire
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);
}
}
}