4
\$\begingroup\$

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?

\$\endgroup\$
1
  • \$\begingroup\$ A better question might be: is using reflection the only solution? \$\endgroup\$
    – Rob
    Nov 18, 2011 at 4:36

1 Answer 1

2
\$\begingroup\$

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;
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.