C # 延迟加载的自动属性

在 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;}
62642 次浏览

我不认为纯 C # 可以做到这一点。但是你可以使用像 PostSharp这样的 IL 重写器来完成。例如,它允许您根据属性在函数之前和之后添加处理程序。

No there is not. Auto-implemented properties only function to implement the most basic of properties: backing field with getter and setter. It doesn't support this type of customization.

However you can use the 4.0 Lazy<T> type to create this pattern

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

这段代码将在第一次调用 Value表达式时惰性地计算 _someVariable的值。它将只计算一次,并将缓存该值以供将来使用 Value属性

不像那样,属性的参数的值必须是常数,不能调用代码(即使是静态代码)。

You may however be able to implement something with PostSharp's Aspects.

看看他们:

PostSharp

也许你能得到的最简洁的方法就是使用空合并运算符:

get { return _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());
}
}

当然,传递函数指针的开销是存在的,但它为我完成了这项工作,与一遍又一遍地运行这个方法相比,我并没有注意到太多的开销。

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

https://github.com/bcuff/AutoLazy uses Fody to give you something like this

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 ReportModel
{
private readonly Func<ReportConfig> _getReportLayout;
public ReportModel(Func<ReportConfig> getReportLayout)
{
_getReportLayout = getReportLayout;
}


private ReportConfig _getReportLayoutResult;
public ReportConfig GetReportLayoutResult => _getReportLayoutResult ?? (_getReportLayoutResult = _getReportLayout());




public string ReportSignatureName => GetReportLayoutResult.ReportSignatureName;
public string ReportSignatureTitle => GetReportLayoutResult.ReportSignatureTitle;
public byte[] ReportSignature => GetReportLayoutResult.ReportSignature;
}

我非常喜欢这个想法,并且想提供下面的 C # 代码片段,我称之为 proplazy.nippet。(您可以导入该文件或将其粘贴到标准文件夹,您可以从 Snippet Manager 获得该文件夹)

下面是它的输出样本:

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

下面是代码片段文件的内容: (另存为 proplazy.nippet)

<?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>

我是这样做的:

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

如果你在惰性初始模式中使用构造函数,下面的扩展可能也会有帮助

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 # 8.0或更高版本,所以你现在可以做得更简洁:

private string _someVariable;


public string SomeVariable => _someVariable ??= SomeClass.IOnlyWantToCallYouOnce();


基于这里的一些答案,我使用 c # 5 CallerMemberName为单行 Lazy 属性创建了自己的类 属性。

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

I added class constraint to restrict use to reference types, for value types i.e. int there is no point as it would return a copy anyway.

public int Prop3 => lc.Get<int>(()=>3);
//Would have the exact same function as this:
public int Prop4 => 3;

I'm surprised no one wrote this? Just initialize the property in the constructor.

    public Lazy<IUtil> Util { get; }


public Foo()
{
this.Util = new Lazy<IUtil>(() => new Util());
}