C#延迟加载的自动属性


100

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

@Gabe:请注意,如果该类从不返回null,则它将仅被调用一次。
RedFilter 2010年

我发现...似乎使用了单例模式
ctorx

Answers:


112

不,那里没有。自动实现的属性仅用于实现最基本的属性:具有getter和setter的后备字段。它不支持这种类型的自定义。

但是,您可以使用4.0 Lazy<T>类型创建此模式

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

此代码将延迟计算_someVariable第一次Value调用表达式的值。它只会被计算一次,并将缓存该值以供该Value属性将来使用


1
实际上,在我看来,Lazy实现了单例模式。那不是我的目标……我的目标是创建一个延迟加载的属性,该属性被延迟实例化,但与它所在的类的实例一起处理。懒惰似乎并没有那样表现。
ctorx

19
@ctorx Lazy与单例模式无关。它确实可以满足您的要求。
2013年

8
注意,SomeClass.IOnlyWantToCallYouOnce在您的示例中,必须与字段初始化程序一起使用,才能为静态。
rory.ap 2016年

很棒的答案。如果您希望拥有许多懒惰的属性,请参阅我的答案以获取Visual Studio片段。
Zephryl

40

您可能获得的最简洁的方法是使用null-coalescing运算符:

get { return _SomeVariable ?? (_SomeVariable = SomeClass.IOnlyWantToCallYouOnce()); }

10
IOnlyWantToCallYouOnce返回的情况下null,它将多次调用它。
JaredPar

9
当使用null-coalescing运算符时,以上示例将失败。正确的语法是:_SomeVariable ?? ( _SomeVariable = SomeClass.IOnlyWantToCallYouOnce() );-注意_SomeVariable如果设置为null,则会在设置周围加上括号。
地铁蓝精灵

这是最好的选择。首先,我使用Lazy<>,但是出于我们的目的,它效果更好。使用最新的C#,它也可以编写得更加简洁,乍一看=> _SomeVariable ?? (_SomeVariable = SomeClass.IOnlyWantToCallYouOnce());可能不会引起您的注意:运算符会评估右侧操作数并返回其结果
RunninglVlan

15

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

在代码的最后一部分中,初始化实际上不是惰性的。IOnlyWantToCallYouOnce每次实例化该类时都会在构造过程中调用它。
Tom Blodget

所以换句话说,这不是延迟加载吗?
Zapnologica

@Zapnologica我以前的回答有点错误,但是我更新了它。SomeVariable懒加载。
亚历山大·德克

该答案更像是表达身体自动属性的音调。
Little Endian

@AbleArcher指出一种新的语言功能现在是一个推销点吗?
亚历山大·德克

5

并非如此,属性的参数值必须恒定,您不能调用代码(即使是静态代码)。

但是,您也许可以使用PostSharp的Aspects实现某些功能。

去看一下:

后锐


5

这是我为您解决问题的方法。基本上,这个想法是一个将在第一次访问时由函数设置的属性,后续访问将产生与第一次访问相同的返回值。

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

当然,传递函数指针会产生开销,但是这对我来说确实有用,与一遍又一遍地运行该方法相比,我没有注意到太多的开销。


将函数提供给构造函数不是更有意义吗?这样,您就不必每次都内联创建它,并且可以在第一次使用它后对其进行处理。
Mikkel R. Lund 2014年

@ lund.mikkel是的,也可以。可能是两种方法的用例。
deepee1

5
如果将函数传递给构造函数(就像.Net的Lazy类一样),则传递的函数必须是静态的,我知道这在很多情况下都不适合我的设计。
嘎吱嘎吱的2014年

@ MikkelR.Lund有时您不想在构造函数中执行某些代码,而只是按需执行(并将结果缓存为后备字段)
mamuesstack

3

我是这个想法的忠实拥护者,并希望提供以下我称为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>

2

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


1

我这样做是这样的:

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

在这种情况下,如何使用“ this”?
Riera

@Riera是什么意思?就像普通财产一样。EG public ISet<String> RegularProperty {get;set} public string CalculatedProperty => this.LazyValue(() => { return string.Join(",", RegularProperty.ToArray()); });
亚历山大Zuban

0

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

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

我像吼叫

result.RaporBilgisi = new ExchangeProgramPersonAllDataModel.RaporImza(() => _reportConfigService.GetReportLayout(documentTypeId));

1
尽管这可以回答作者的问题,但它缺少一些解释性的文字和文档链接。没有一些短语,原始代码片段不是很有帮助。您可能还会发现如何写一个好的答案很有帮助。请修改您的答案。
hellow

0

如果您在延迟初始化期间使用构造函数,则以下扩展名也可能会有所帮助

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>()); */

使用帮手有好处LazyInitializer.EnsureInitialized()吗?因为据我所知,除了上述功能之外,LazyInitializer还提供了错误处理以及同步功能。LazyInitializer源代码
semaj1919

0

运算符?? =在C#8.0及更高版本中可用,因此您现在可以更加简洁:

private string _someVariable;

public string SomeVariable => _someVariable ??= SomeClass.IOnlyWantToCallYouOnce();
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.