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".