Lazy
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:
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);
}
}
There is an other problem with Lazy
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
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:
// https://www.siepman.nl/Home/Blog/363e34a3-f2a0-4be9-ada9-a6a2e6b07672
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();
}
}
To make it really easy for the user of the two classes LazyValue
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:
// https://www.siepman.nl/Home/Blog/363e34a3-f2a0-4be9-ada9-a6a2e6b07672
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: