I’ve defined a generic class “Lazy<T>“, for lazy evaluation and caching of the result of a delegate Func<T>.
I also define two implicit cast operators so I can create a Lazy<T> from a Func<T>s, and I can assign a Lazy<T> to a T (gets the Value of the Lazy<T>)
The idea is that you can pass around a Lazy<T> in place of an instance of T, but not do the work to calculate/retrieve the value until it is assigned to an actual instance of T.
// class Lazy<T>
// Encapsulates a value which can be retrieved when first accessed,
// and is then cached.
class Lazy<T>
{
private Func<T> _getter;
private T _cached;
private bool _isCached;
// Get/set the getter delegate
// that 'calculates' the value.
public Func<T> Getter
{
get
{
return _getter;
}
set
{
_getter = value;
_cached = default(T);
_isCached = false;
}
}
// Get/set the value.
public T Value
{
get
{
if (!_isCached)
{
_cached = Getter();
_isCached = true;
_getter = null;
}
return _cached;
}
set
{
_cached = value;
_isCached = true;
_getter = null;
}
}
// Implicit casts:
// Create a T from a Lazy<T>
public static implicit operator T(Lazy<T> lazy)
{
return lazy.Value;
}
// Create a Lazy<T> from a Func<T>
public static implicit operator Lazy<T>(Func<T> getter)
{
return new Lazy<T> {Getter = getter};
}
}
But this class doesn’t work as I expected in one case, highlighted in the test app below:
class Program
{
static void Main()
{
// This works okay (1)
TestLazy(() => MakeStringList());
// This also works (2)
Lazy<string> lazyString = new Func<string>(() => "xyz");
string s = lazyString;
//This doesn't compile (3)
//
Lazy<IList<string>> lazyStrings = new Func<IList<string>>(MakeStringList);
IList<string> strings = lazyStrings; //ERROR
}
static void TestLazy<T>(Func<T> getter)
{
Lazy<T> lazy = getter;
T nonLazy = lazy;
}
private static IList<string> MakeStringList()
{
return new List<string> { new string('-', 10) };
}
}
On the line marked with //ERROR, I get a compile error:
error CS0266: Cannot implicitly convert type Lazy<System.Collections.Generic.IList<string>> to System.Collections.Generic.IList<string>. An explicit conversion exists (are you missing a cast?)
This error is confusing as there does exist an implicit cast from the source to the target type in question.
And, on the face of it, code chunk (3) is doing the same thing as (1)
Also, it differs from (2) only by the type used to specialize the Lazy.
Can anyone explain to me what’s going on here?
The problem is that you’re trying to convert to
IList<T>implicitly, andIList<T>isn’t encompassed byIList<T>(even though they’re the same type) – only conversions to non-interface types are considered in encompassing. From section 6.4.3 of the C# 3.0 spec:In section 6.4.4, talking about user defined conversions, one of the steps is (emphasis mine):
IList<T>isn’t encompassed byIList<T>, therefore this step fails.The compiler will do “chained” implicit conversions in other scenarios though – so if you actually had a
Lazy<List<T>>you could write:works, because
List<T>is encompassed byobject(as both are classes, and there’s a standard implicit conversion fromList<T>toobject).Now as for why this is the case, I suspect it’s to stop odd cases where you’d expect a reference conversion, but you would actually get the implicit conversion. Suppose we had:
Which conversion should be used? There’s an implicit reference conversion from the actual type to
IList<string>… but the compiler doesn’t know that, because the expression is of typeLazy<IList<string>>. Basically interfaces are awkward because they can show up later in the type hierarchy, whereas with a class you always know where you are, if you see what I mean. (Implicit conversions which involve two classes in the same hierarchy are prohibited.)