[英]C# Lazy Loaded Automatic Properties
在C#,
有没有办法将自动属性转换为具有指定默认值的延迟加载自动属性?
本质上,我想把这个...
private string _SomeVariable
public string SomeVariable
{
get
{
if(_SomeVariable == null)
{
_SomeVariable = SomeClass.IOnlyWantToCallYouOnce();
}
return _SomeVariable;
}
}
变成不同的东西,我可以在其中指定默认值,它会自动处理 rest ...
[SetUsing(SomeClass.IOnlyWantToCallYouOnce())]
public string SomeVariable {get; private set;}
不,没有。 自动实现的属性仅用于实现最基本的属性:带有 getter 和 setter 的支持字段。 它不支持这种类型的自定义。
但是,您可以使用 4.0 Lazy<T>
类型来创建此模式
private Lazy<string> _someVariable =new Lazy<string>(SomeClass.IOnlyWantToCallYouOnce);
public string SomeVariable => _someVariable.Value;
此代码将在第一次调用Value
表达式时延迟计算_someVariable
的Value
。 它只会计算一次,并将缓存值以供将来使用Value
属性
您可以获得的最简洁的方法可能是使用空合并运算符:
get { return _SomeVariable ?? (_SomeVariable = SomeClass.IOnlyWantToCallYouOnce()); }
C#6 中有一个名为Expression Bodied Auto-Properties的新功能,它可以让您更简洁地编写它:
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;
}
运算符??=在 C# 8.0 及更高版本中可用,因此您现在可以更简洁地使用它:
private string _someVariable;
public string SomeVariable => _someVariable ??= SomeClass.IOnlyWantToCallYouOnce();
这是我对您的问题的解决方案的实现。 基本上这个想法是一个属性,它将在第一次访问时由函数设置,随后的访问将产生与第一次相同的返回值。
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;
});
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);
}
}
}
[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>()); */
根据这里的一些答案,我使用 c#5 CallerMemberName
属性为单行惰性属性制作了自己的 class。
public class LazyContainer
{
private Dictionary<string, object> _LazyObjects = new Dictionary<string, object>();
public T Get<T>(Func<T> factory, [CallerMemberName] string name = null) where T: class
{
if (string.IsNullOrEmpty(name))
return default(T);
if (!_LazyObjects.ContainsKey(name))
_LazyObjects.Add(name, new Lazy<T>(factory, true));
return ((Lazy<T>)_LazyObjects[name]).Value;
}
public T Get<T>([CallerMemberName] string name = null) where T : class,new()
{
return Get(() => new T(), name);
}
}
它可以这样使用:
class PropertyClass
{
private LazyContainer lc = new LazyContainer();
public SimpleClass Prop1 => lc.Get<SimpleClass>();
public LessSimpleClass Prop2 => lc.Get<LessSimpleClass>(()=> new LessSimpleClass(someParametrs...));
}
我添加了class
约束来限制对引用类型的使用,对于值类型,即int
没有意义,因为它无论如何都会返回一个副本。
public int Prop3 => lc.Get<int>(()=>3);
//Would have the exact same function as this:
public int Prop4 => 3;
我很惊讶没有人写这个? 只需在构造函数中初始化属性。
public Lazy<IUtil> Util { get; }
public Foo()
{
this.Util = new Lazy<IUtil>(() => new Util());
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.