C# Lazy Loaded Automatic Properties

Viewed 61370

In C#,

Is there a way to turn an automatic property into a lazy loaded automatic property with a specified default value?

Essentially, I am trying to turn this...

private string _SomeVariable

public string SomeVariable
{
     get
     {
          if(_SomeVariable == null)
          {
             _SomeVariable = SomeClass.IOnlyWantToCallYouOnce();
          }

          return _SomeVariable;
     }
}

into something different, where I can specify the default and it handles the rest automatically...

[SetUsing(SomeClass.IOnlyWantToCallYouOnce())]
public string SomeVariable {get; private set;}
13 Answers

Operator ??= is available using C# 8.0 and later, so you can now do it even more concise:

private string _someVariable;

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

I did it like this:

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

and later you can use it like

       public virtual bool SomeProperty => this.LazyValue(() =>
    {
        return true; 
    });

I'm a big fan of this idea, and would like to offer up the following C# snippet which I called proplazy.snippet.(you can either import this or paste it into the standard folder which you can get from the Snippet Manager)

Here's a sample of its output:

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

Here's the snippet file contents: (save as 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>
[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;
}

If you use a constructor during lazy initialization, following extensions may be helpful too

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

Based on some answers here i made my own class for one-liner Lazy properties using c#5 CallerMemberName Attribute.

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

It can be used like this:

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