Problem
The Problem
I found myself needing an instance of an IObjectContext
interface that should never be null, but wanting to delay instantiation until after some required resources have been loaded.
I decided to approach this with Lazy<T>
, but quickly ran into an issue — I also require an IObjectSet<TEntity>
that is never null and lazy instantiated. The problem is this:
// lazyObjectSet needs to be created directly after lazyContext, but I need to
// delay instantiating both. The desired factory method exists on lazyContext,
// but accessing lazyContext.Value instantiates context before it is needed:
//
var lazyObjectSet = new Lazy<IObjectSet<TEntity>>(lazyContext.Value.FactoryMethodName);
My Solution
I would like suggestions for improvements on my approach, or alternative approaches to the problem.
public static class LazyExtensions {
private class LazyFactory<T, TResult> where TResult : class {
private readonly Lazy<T> _dependency;
private readonly MethodInfo _factory;
public LazyFactory(Lazy<T> lazyDependency, MethodInfo lazyFactory) {
// Argument null checks omitted for brevity
Contract.Requires(typeof(TResult).Equals(lazyFactory.ReturnType));
Contract.Requires(lazyFactory.GetParameters().Count() == 0);
_factory = lazyFactory;
_dependency = lazyDependency;
}
public TResult Invoke() {
return (TResult) _factory.Invoke(_dependency.Value, null);
}
}
public static Func<TOut> GetLazyFactory<TIn, TOut>(
Lazy<TIn> dependency, MethodInfo factoryInfo) where TOut : class {
return new LazyFactory<TIn, TOut>(dependency, factoryInfo).Invoke;
}
}
Now I can use GetLazyFactory()
to write the following, and the context should not be instantiated until lazyObjectSet.Value
is accessed:
var lazyFactory = LazyExtensions.GetLazyFactory<IObjectContext, IObjectSet<TEntity>>(
new Lazy<IObjectContext>(factory.Create),
typeof (IObjectContext).GetMethod("ObjectSet"));
var lazyObjectSet = new Lazy<IObjectSet<TEntity>>(lazyFactory);
How can this be improved, and is there a better way of doing this?
Solution
In conjunction with the rest of the code, the following line exhibits a bug:
Contract.Requires(typeof(TResult).Equals(lazyFactory.ReturnType));
The problem is that my IObjectContext.ObjectSet()
method has the generic return type IObjectSet<TEntity>
, and lazyFactory
doesn’t have type information for TEntity
.
Specifically, the type parameter for lazyFactory
is unknown because it’s obtained using GetMethod()
. As a result, Equals()
returns false and the constraint fails with an exception.
I solved the problem by modifying GetLazyFactory
to accept a method name string and obtain the MethodInfo
itself, calling MakeGenericMethod()
as necessary.
public static Func<TOut> GetLazyFactory<TIn, TOut>(Lazy<TIn> dependency, string factoryName)
where TOut : class
{
Type outT = typeof(TOut); // Expected return type of factory method
MethodInfo factoryMethodInfo = // Should return TOut
typeof(TIn).GetMethod(factoryName);
if(outT.IsGenericType)
{
// Get generic type argument of TOut, making sure exactly 1 exists.
Type tParam = outT.GetSingleGenericArgument();
// We must specify the generic type or Type.Equals() will yield false.
factoryMethodInfo = factoryMethodInfo.MakeGenericMethod(tParam);
}
return new LazyFactory<TIn, TOut>(dependency, factoryMethodInfo).Invoke;
}