How do I register a delegate with the non-generic Register method?

Coordinator
Jan 13, 2015 at 2:27 PM
I can do this:
var container = new Container();

container.Register<Service>();
container.RegisterSingle<Func<Service>>(() => 
    container.GetInstance(typeof(Service)) as Service);

var result = container.GetInstance<Func<Service>>()();
But not this:
var container = new Container();

container.Register<Service>();
container.RegisterSingle(typeof(Func<Service>), () => 
    container.GetInstance(typeof(Service)) as Service);

var result = container.GetInstance<Func<Service>>()();
The second block of code gives an error:
SimpleInjector.ActivationException: The registered delegate for type Func<Service> threw an exception. Unable to cast object of type 'Service' to type 'System.Func`1[ConsoleApplication1.Service]'.
Can you tell me the obvious thing I am missing?
Coordinator
Jan 13, 2015 at 2:54 PM
It took a moment to realize what went wrong, but you are basically doing this:
container.RegisterSingle(typeof(Func<Service>), container.GetInstance<Service>);
Which is basically the same as:
Func<Service> serviceFactory = container.GetInstance<Service>;
container.RegisterSingle(typeof(Func<Service>), serviceFactory);
With the RegisterSingle(Type, Func<object>) overload you register a Func<object> delegate that returns an instance of the type that is supplied as the first argument. Read this previous line again and now look closely at what you are registering.

Since the supplied type is of Func<Service> the second argument should be a delegate that returns a Func<Service>. However, the delegate you are registering returns Service.

So this will work:
Func<Service> serviceFactory = container.GetInstance<Service>;
Func<Func<Service>> serviceServiceFactory = () => serviceFactory;
container.RegisterSingle(typeof(Func<Service>), serviceServiceFactory);
Because you use the non-generic RegisterSingle overload that takes in a Func<object>, Simple Injector will not be able to check the correctness of your registration until the moment of verification or resolving.
Coordinator
Jan 16, 2015 at 12:24 PM
Edited Jan 16, 2015 at 12:30 PM
This is what I was trying to do:
private static void RegisterDelegate(Container container, Type service)
{
    var type = typeof(Func<>).MakeGenericType(service);
    var factory = GetExpressionToInvokeDelegate(
        GetContainerGetInstanceForType((container, service));
    container.RegisterSingle(type, () => factory);
}

private static Delegate GetExpressionToInvokeDelegate(Delegate d)
{
    var mi = d.GetType().GetMethod("Invoke");

    var call = Expression.Call(Expression.Constant(d), mi);

    return Expression.Lambda(call).Compile();
}

private static Delegate GetContainerGetInstanceForType(Container container, Type type)
{
    var mi = typeof(Container)
        .GetMethod("GetInstance", new Type[] { })
        .MakeGenericMethod(type);

    var call = Expression.Call(
        Expression.Constant(container),
        mi);

    var d = Expression.Lambda(call).Compile();

    return d;
}
Coordinator
Jan 16, 2015 at 12:38 PM
Wouldn't that be the same as this:
public static FactoryExtensions {
    public static void RegisterFactory<TService>(this Container container) {
        container.RegisterSingle<Func<TService>>(container.GetInstance<TService>);
    }

    public static void RegisterFactory(this Container container, Type serviceType) {
        var method = (
            from method in typeof(FactoryExtensions).GetMethods()
            where method.name == "RegisterFactory" && method.GetParameters().Length == 1
            select method).Single();

        method.MakeGenericMethod(serviceType).Invoke(null, container);
    }
}
But if you use this, you can also do this:
container.AllowResolvingFuncFactories();
Coordinator
Jan 16, 2015 at 1:04 PM
Sweet! I knew it was worth posting my attempt so you could show me a simple technique.

Thanks.
Coordinator
Jan 16, 2015 at 1:18 PM
Edited Jan 16, 2015 at 1:21 PM
And this is an optimized version:
public static void RegisterFactory<TService>(this Container container) {
    InstanceProducer producer = null;
    container.RegisterSingle<Func<TService>>(() => {
        if (producer == null) producer = container.GetRegistration(typeof(TService), true);
        return (TService)producer.GetInstance();
    });
}
Marked as answer by qujck on 1/16/2015 at 7:20 AM