Providing factory method to Lazy when factory exists on another lazy instance?

Posted on

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;
}

Leave a Reply

Your email address will not be published. Required fields are marked *