I'd like to get some feedback on a class I wrote for encapsulating the lazy-load pattern. Usually, when I do lazy-loading, it looks something like this:
private SomeClass _someProperty;
public SomeClass SomeProperty
{
get
{
if(this._someProperty == null)
{
// run code to populate/instantiate _someProperty
}
return this._someProperty;
}
}
The way the class works is pretty basic - it accepts a generic type parameter and one or more method delegates using Func<T>. If more than one delegate is specified, the first one to return a non-default value is used (this particular feature may not be useful to everyone, but it was necessary in the domain I was using it).
Here is a brief excerpt that I believe shows pretty much what the class does:
/// <summary>
/// A container for an object of type <typeparamref name="T"/> whose value is not known until the <see cref="Value" /> property is accessed.
/// </summary>
/// <typeparam name="T">The type of the underlying object.</typeparam>
public class Lazy<T>
{
/// <summary>
/// A value representing whether the delegates have been invoked.
/// </summary>
private Boolean _wereValueDelegatesInvoked;
/// <summary>
/// Initializes a new instance of the <see cref="Lazy{T}" /> class with the specified <paramref name="valueDelegates"/>.
/// </summary>
/// <param name="valueDelegates">A list of delegates that can potentially return the value of the underlying object.</param>
public Lazy(params Func<T>[] valueDelegates)
{
this.ValueDelegates = valueDelegates;
}
/// <summary>
/// Gets the delegate that returns the value of the underlying object.
/// </summary>
public IEnumerable<Func<T>> ValueDelegates { get; protected set; }
private T _value;
/// <summary>
/// Gets the value of the underlying object. If multiple delegates are specified, the first delegate to return a non-default value will be used.
/// </summary>
public T Value
{
get
{
if (!this._wereValueDelegatesInvoked)
{
this._value = this.GetValue();
}
return this._value;
}
}
/// <summary>
/// Return the value of the underlying object. If multiple delegates are specified, the first delegate to return a non-default value will be used.
/// </summary>
/// <returns>The value of the underlying object.</returns>
protected T GetValue()
{
T value = default(T);
if (this.ValueDelegates != null)
{
foreach (Func<T> valueDelegate in this.ValueDelegates)
{
value = valueDelegate.Invoke();
if (!Lazy<T>.IsNullOrDefault(value)) break;
}
}
this._wereValueDelegatesInvoked = true;
return value;
}
}
...and for those for whom this will not evoke a response of "tl;dr", here is the class in its entirety:
/// <summary>
/// A container for an object of type <typeparamref name="T"/> whose value is not known until the <see cref="Value" /> property is accessed.
/// </summary>
/// <typeparam name="T">The type of the underlying object.</typeparam>
public class Lazy<T>
{
/// <summary>
/// A value representing whether the delegates have been invoked.
/// </summary>
private Boolean _wereValueDelegatesInvoked;
/// <summary>
/// Initializes a new instance of the <see cref="Lazy{T}" /> class with the specified <paramref name="valueDelegate"/>.
/// </summary>
/// <param name="valueDelegate">A delegate that returns the value of the underlying object.</param>
public Lazy(Func<T> valueDelegate)
{
this.ValueDelegates = new Func<T>[] { valueDelegate };
}
/// <summary>
/// Initializes a new instance of the <see cref="Lazy{T}" /> class with the specified <paramref name="valueDelegates"/>.
/// </summary>
/// <param name="valueDelegates">A list of delegates that can potentially return the value of the underlying object.</param>
public Lazy(IEnumerable<Func<T>> valueDelegates)
{
this.ValueDelegates = valueDelegates;
}
/// <summary>
/// Initializes a new instance of the <see cref="Lazy{T}" /> class with the specified <paramref name="valueDelegates"/>.
/// </summary>
/// <param name="valueDelegates">A list of delegates that can potentially return the value of the underlying object.</param>
public Lazy(params Func<T>[] valueDelegates)
{
this.ValueDelegates = valueDelegates;
}
public Lazy(T value)
{
this._value = value;
this._wereValueDelegatesInvoked = true;
}
/// <summary>
/// Gets the delegate that returns the value of the underlying object.
/// </summary>
public IEnumerable<Func<T>> ValueDelegates { get; protected set; }
private T _value;
/// <summary>
/// Gets the value of the underlying object. If multiple delegates are specified, the first delegate to return a non-default value will be used.
/// </summary>
public T Value
{
get
{
if (!this._wereValueDelegatesInvoked)
{
this._value = this.GetValue();
}
return this._value;
}
}
/// <summary>
/// Return the value of the underlying object. If multiple delegates are specified, the first delegate to return a non-default value will be used.
/// </summary>
/// <returns>The value of the underlying object.</returns>
protected T GetValue()
{
T value = default(T);
if (this.ValueDelegates != null)
{
foreach (Func<T> valueDelegate in this.ValueDelegates)
{
value = valueDelegate.Invoke();
if (!Lazy<T>.IsNullOrDefault(value)) break;
}
}
this._wereValueDelegatesInvoked = true;
return value;
}
private static Boolean IsNullOrDefault(T value)
{
if (value == null) return true;
else if (value.Equals(default(T))) return true;
else return false;
}
public override Boolean Equals(Object obj)
{
if (this.Value == null) return (obj == null);
return this.Value.Equals(obj);
}
public override Int32 GetHashCode()
{
if (this.Value == null) return base.GetHashCode();
else return this.Value.GetHashCode();
}
/// <summary>
/// Returns the value of the <see cref="Lazy{T}" />.
/// </summary>
/// <param name="value">A <see cref="Lazy{T}" /> object.</param>
/// <returns>The value of the <see cref="Value" /> property for the <paramref name="value" /> parameter.</returns>
public static implicit operator T(Lazy<T> value)
{
return value.Value;
}
/// <summary>
/// Returns a <see cref="String" /> that represents the current <see cref="Lazy{T}" />.
/// </summary>
/// <returns>A <see cref="String" /> that represents the current <see cref="Lazy{T}" />.</returns>
public override String ToString()
{
if (Lazy<T>.IsNullOrDefault(this.Value)) return null;
else return this.Value.ToString();
}
}
The class is used like this:
[TestMethod]
public void ABadFakeWorkExample()
{
var myLazyObject = new Lazy<Object>((() => { Thread.Sleep(5000); return new Object(); }));
Assert.IsNotNull(myLazyObject);
}
And an example using multiple delegates:
[TestMethod]
public void LazyUsesFirstNonDefaultResult()
{
Lazy<DateTime> lazy = new Lazy<DateTime>(
(() => { return default(DateTime); }),
(() => { return DateTime.Now; }),
(() => { return default(DateTime); }));
Assert.AreNotEqual(default(DateTime), lazy.Value, "D'OH!");
}
So here's what I'd like to know:
- Is this overkill? I mean it's easy enough to create a snippet for the first pattern I posted, right? And if it is, is it so much so that this class shouldn't be used?
- I ran some preliminary tests in ANTS, and this seems to be fairly lightweight, but are there any performance issues I should be worried about?
- Anything that could be added?
- Anything that should be taken away?
Note unrelated to the actual question: Based on feedback I got from this question, I'm leaving the wiki box unticked... Please comment if you really think this should be wikified.