HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

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

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
methodfactoryexistsprovidinginstancelazyanotherwhen

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, but quickly ran into an issue -- I also require an IObjectSet 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>(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 where TResult : class {
        private readonly Lazy _dependency;
        private readonly MethodInfo _factory;

        public LazyFactory(Lazy 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 GetLazyFactory(
        Lazy dependency, MethodInfo factoryInfo) where TOut : class {
        return new LazyFactory(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>(
    new Lazy(factory.Create), 
    typeof (IObjectContext).GetMethod("ObjectSet"));

var lazyObjectSet = new Lazy>(lazyFactory);


How can t

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, 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 GetLazyFactory(Lazy 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(dependency, factoryMethodInfo).Invoke;
}

Code Snippets

Contract.Requires(typeof(TResult).Equals(lazyFactory.ReturnType));
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;
}

Context

StackExchange Code Review Q#6124, answer score: 2

Revisions (0)

No revisions yet.