Lazy<T> property caching alternative

by Alex Siepman 17. March 2014 20:50

Lazy<T>, property cache must often be implemented in constructor

Lazy<T> is a handy class, available since .NET 4.0. It has only one big disadvantage: if you use it to cache a property and it uses other members of the class, you need to implement the property in the constructor. An example:

class Test
{
    private readonly Lazy<int> _someValue =
        new Lazy<int>(GetSomeValue); // Does not compile
    
    public int SomeValue
    {
        get
        {
            return _someValue.Value;
        }
    }

    private int GetSomeValue()
    {
        // Not static, calls other properties
        throw new NotImplementedException();
    }
}

This does not compile because GetSomeValue() is not static and fields are intitialized before the objected is constructed. (A field initializer cannot reference the non-static field, method, or property).

A solution could be to initialize the property in the constructor:

class Test
{
    public Test()
    {
        _someValue = new Lazy<int>(GetSomeValue); 
    }

    private readonly Lazy<int> _someValue;
    public int SomeValue
    {
        get
        {
            return _someValue.Value;
        }
    }

    private int GetSomeValue()
    {
        // Not static, calls other properties
        throw new NotImplementedException();
    }
}

But this has some disadvantages:

  • You need to care about the sequence of code in the constructor. (Not calling something that is not already initialiazed)
  • When you view the code of a property, you can not see how the property is implemented.

Implement the property in the property, not in the constructor

The solution is create a lazy alternative that has a parameterless constructor:

public class LazyValue<T>
{
    private T _value;
    public bool IsInitialized { get; private set; }
    private readonly object _lock = new object();

    public T GetValue(Func<T> producer)
    {
        if (producer == null)
        {
            throw new ArgumentNullException("producer");
        }

        if (!IsInitialized)
        {
            lock (_lock)
            {
                if (!IsInitialized)
                {
                    _value = producer();
                    IsInitialized = true;
                }
            }
        }
        return _value;
    }

    internal T Value
    {
        get
        {
            if (!IsInitialized)
            {
                throw new FieldAccessException("This value needs to be set by using the GetValue() method or setting the Value property");
            }
            return _value;
        }
    }
}

Now you can implement the property just like this:

private readonly LazyValue<int> _someValue = new LazyValue<int>();
public int SomeValue
{
    get
    {
        return _someValue.GetValue(GetSomeValue);
    }
}

private int GetSomeValue()
{
    // Not static, calls other properties
    throw new NotImplementedException();
}

Or without a seperate Get-method like in this example:

private readonly LazyValue<Guid> _id = new LazyValue<Guid>();
public Guid Id
{
    get
    {
        return _id.GetValue(Guid.NewGuid);
    }
}

Lazy<T> with IEnumerable, a separate lazy class

There is an other problem with Lazy<T>. In the next class Lazy<T> is useless:

class Test
{
    private readonly Lazy<IEnumerable<Guid>> _someValues =
        new Lazy<IEnumerable<Guid>>(GetSomeValues);

    public IEnumerable<Guid> SomeValues
    {
        get
        {
            return _someValues.Value;
        }
    }

    private static IEnumerable<Guid> GetSomeValues()
    {
        for (int i = 0; i < 40; i++)
        {
            yield return Guid.NewGuid();
        }
    }
}

When running this code...

var t = new Test();
foreach (var value in t.SomeValues.Take(3))
{
    Console.WriteLine(value);
}
foreach (var value in t.SomeValues.Take(3))
{
    Console.WriteLine(value);
}

... it returns 6 different Guid's! (And not: guid 4, 5 and 6 are the same as 1, 2 and 3). The "solution" with Lazy<T> is to add ".ToList()" to the constructor in the field:

private readonly Lazy<IEnumerable<Guid>> _someValues =
    new Lazy<IEnumerable<Guid>>(() => GetSomeValues().ToList());

That is not really lazy because it creates 40 Guid's to show only 3 Guid's!

The solution is to use this class:

public class LazyEnumerableValue<T>
{
    // LazyList<T>, see other post:
    // http://www.siepman.nl/blog/post/2013/10/09/LazyList-A-better-LINQ-result-cache-than-List.aspx
    private readonly LazyValue<LazyList<T>> _cache = new LazyValue<LazyList<T>>();

    public IEnumerable<T> GetValue(Func<IEnumerable<T>> producer)
    {
        return _cache.GetValue(() => CacheEnumerable(producer));
    }

    private LazyList<T> CacheEnumerable(Func<IEnumerable<T>> producer)
    {
        var value = producer();
        var result = value.ToLazyList();
        return result;
    }

    public bool AllElementsAreCached
    {
        get
        {
            return _cache.IsInitialized && _cache.Value.AllElementsAreCached;
        }
    }
}

Now it works as simple as this:

private readonly LazyEnumerableValue<Guid> _someValues =
    new LazyEnumerableValue<Guid>();

public IEnumerable<Guid> SomeValues
{
    get
    {
        return _someValues.GetValue(GetSomeValues);
    }
}

private static IEnumerable<Guid> GetSomeValues()
{
    for (int i = 0; i < 40; i++)
    {
        yield return Guid.NewGuid();
    }
}

Combine the 2 classes, the ultimate caching class

To make it really easy for the user of the two classes LazyValue<T> and LazyEnumerableValue<T>, I have combined those two classes. If the value implements IEnumerable<T> but not IList<T>, it automatically converts the IEnumerable to a LazyList<T> with a 'dynamic trick' so it is only enumerated once and not more than necessary. The disadvantage is that the first call of GetValue() is slightly slower.

public class LazyValue<T>
{
    private T _value;
    public bool IsInitialized { get; private set; }
    private readonly object _lock = new object();

    public T GetValue(Func<T> producer)
    {
        if (producer == null)
        {
            throw new ArgumentNullException("producer");
        }

        if (!IsInitialized) //  Double-checked locking pattern
        {
            lock (_lock) 
            {
                if (!IsInitialized) //  Double-checked locking pattern
                {
                    _value = ConvertToListIfNecessary(producer());

                    IsInitialized = true;
                }
            }
        }
        return _value;
    }

    private T ConvertToListIfNecessary(dynamic value)
    {
        return MaybeToList(value);
    }

    private LazyList<TP> MaybeToList<TP>(IEnumerable<TP> value)
    {
        // LazyList<T>, see other post:
        // http://www.siepman.nl/blog/post/2013/10/09/LazyList-A-better-LINQ-result-cache-than-List.aspx
        return new LazyList<TP>(value);
    }

    private IList<TP> MaybeToList<TP>(IList<TP> value)
    {
        return value;
    }

    private object MaybeToList(object value)
    {
        return value;
    }
}

Credits:
I used input from two people for this post:

  • Frank Bakker for the initial "GetValue(Func<T> producer)" solution. (No longer a func in the constructor like Lazy<T>).
  • Jon Skeet for the "Dynamic trick".

Add comment

  Country flag

biuquote
  • Comment
  • Preview
Loading

About the author

I am a software architect at Roxit and also a C# Developer. My main interests in the area of ​​C# are LINQ and generics

Visit my personal homepage (Dutch) for more info about me.

Month List

Page List