C#では、
指定されたデフォルト値を使用して、自動プロパティを遅延ロードされた自動プロパティに変える方法はありますか?
本質的に、私はこれを変えようとしています...
private string _SomeVariable
public string SomeVariable
{
get
{
if(_SomeVariable == null)
{
_SomeVariable = SomeClass.IOnlyWantToCallYouOnce();
}
return _SomeVariable;
}
}
デフォルトを指定でき、残りは自動的に処理されます...
[SetUsing(SomeClass.IOnlyWantToCallYouOnce())]
public string SomeVariable {get; private set;}
いいえ、ありません。自動実装プロパティは、最も基本的なプロパティであるゲッターとセッターを持つバッキングフィールドを実装するためにのみ機能します。このタイプのカスタマイズはサポートしていません。
ただし、4.0 Lazy<T>
タイプを使用してこのパターンを作成できます
private Lazy<string> _someVariable =new Lazy<string>(SomeClass.IOnlyWantToCallYouOnce);
public string SomeVariable => _someVariable.Value;
このコードは、Value
式が最初に呼び出されたときに、_someVariable
の値を遅延計算します。一度だけ計算され、将来のValue
プロパティの使用のために値をキャッシュします
おそらくあなたが得ることができる最も簡潔なものは、null合体演算子を使用することです:
get { return _SomeVariable ?? (_SomeVariable = SomeClass.IOnlyWantToCallYouOnce()); }
Expression Bodied Auto-Properties と呼ばれるC#6の新機能があります。 :
public class SomeClass
{
private Lazy<string> _someVariable = new Lazy<string>(SomeClass.IOnlyWantToCallYouOnce);
public string SomeVariable
{
get { return _someVariable.Value; }
}
}
次のように書くことができます:
public class SomeClass
{
private Lazy<string> _someVariable = new Lazy<string>(SomeClass.IOnlyWantToCallYouOnce);
public string SomeVariable => _someVariable.Value;
}
そうではなく、属性のパラメーターは値が一定でなければならず、コード(静的コードでも)を呼び出すことはできません。
ただし、PostSharpのアスペクトを使用して何かを実装できる場合があります。
それらをチェックしてください:
ここにあなたの問題を解決する私の実装があります。基本的には、最初のアクセス時に関数によって設定されるプロパティであり、後続のアクセスでは最初と同じ戻り値が生成されます。
public class LazyProperty<T>
{
bool _initialized = false;
T _result;
public T Value(Func<T> fn)
{
if (!_initialized)
{
_result = fn();
_initialized = true;
}
return _result;
}
}
次に使用する:
LazyProperty<Color> _eyeColor = new LazyProperty<Color>();
public Color EyeColor
{
get
{
return _eyeColor.Value(() => SomeCPUHungryMethod());
}
}
もちろん、関数ポインタを渡すオーバーヘッドがありますが、それは私のために仕事をしてくれますし、メソッドを何度も実行するのに比べてあまりオーバーヘッドがありません。
私はこのアイデアの大ファンであり、proplazy.snippetと呼ばれる次のC#スニペットを提供したいと思います(これをインポートするか、スニペットマネージャーから取得できる標準フォルダーに貼り付けることができます)。
出力のサンプルは次のとおりです。
private Lazy<int> myProperty = new Lazy<int>(()=>1);
public int MyProperty { get { return myProperty.Value; } }
スニペットファイルの内容は次のとおりです(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>
これは純粋なC#では不可能だと思います。ただし、 PostSharp のようなILリライタを使用して実行できます。たとえば、属性に応じて関数の前後にハンドラーを追加できます。
私はこのようにしました:
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;
}
}
後であなたはそれを次のように使うことができます
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;
}
そして、私は怒鳴るような呼び出します
result.RaporBilgisi = new ExchangeProgramPersonAllDataModel.RaporImza(() => _reportConfigService.GetReportLayout(documentTypeId));
遅延初期化中にコンストラクターを使用する場合、以下の拡張も役立つ場合があります
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));
}
使用法
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 はFodyを使用してこのようなものを提供します
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);
}
}
}