We have Lazy Resolution; how about Greedy Resolution?

Feb 27, 2009 at 11:26 PM
This'll fly in the face of Funq's "raw speed" concept, but since it would be a separate method (and possibly wrapped in a #if and only available on the desktop framework) I think it might be worth suggesting.

It should be possible to automatically resolve any constructor parameters for the type I try to resolve by checking if their types have previously been registered. Basically you could iterate over all the constructors for TService using reflection, and keep track of the ones who have parameters that you can resolve. Then when you're done, use the one with the most parameters (the "greediest" one) to instantiate the type.

So if I do this:

    container.Register<IBar>(c => new Bar());
    container.GreedyResolve<Foo>();

... that should be able to get me back an instance of Foo, despite it not being registered, because it has a constructor that takes an IBar.

What do you think? I know that StructureMap does this by default, but since it would affect performance I'd be happy for it to be a separate method.
Coordinator
Feb 28, 2009 at 12:27 AM
Mmm... dunno...
What if Foo receives ctor arguments such as a string, int, bool, etc? Where would it get those from?
It can quickly become a dubious value, I think...
Wouldn't it be better if you had codegen'ed modules that would allow you to register everything with the ctors you choose?3
Feb 28, 2009 at 12:47 AM
Yeah I don't think StructureMap works either for types that you haven't explicitly registered (like value types). It's more for types that only take dependencies in their constructors. See Rob Conery's MVC Storefront screencast - one of the earlier episodes was all about IoC containers and he had Jeremy Miller walk him through StructureMap. That's where I first saw this behaviour.
Jun 4, 2009 at 12:20 AM

Kzu,

As a learning exercise I have implemented AutoResolve<T>(), but I can't produce a patch right now 'coz the patch would include my (already submitted) IsRegistered<T> implementation.

For the sake of this exercise, AutoResolve<T> works like this:

  1. If T has been registered, resolve it and return the instance
  2. Find the constructor on T that has the most parameters, where each parameter's type has previously been registered
  3. Recursively call AutoResolve for each of the parameters, and then use Activator.CreateInstance to return an instance of T with those parameters

It seems to work here. For value types (as well as string and DateTime) I am using IsRegistered to make sure those types have been explicitly registered. Otherwise I move on to the next constructor.

I can post the implementation here (it's three methods - about 70 lines) if you like.

Matt

Jul 7, 2009 at 5:59 AM
Edited Jul 7, 2009 at 6:07 AM

I wrote something that is along the same lines, sort of...

I call it EasyRegister.

Basically it is a shortcut extension method to do:

 

container.Register<ISomething>(c=>new Something (c.Resolve<IDependency>(), c.Resolve<IAnotherDependency>()));

 

... with any number of constructor arguments.

someone else may have already implemented this as well, I'm not sure. I think the performance should be good because it is creating an expression tree that is (I think?) equivalent to the above code and passing that to the normal container Register method. (But I have not tested performance at all).

I posted it here:

http://pastie.org/536675

 

Let me know if I should submit a patch...

Jul 7, 2009 at 6:06 AM

I like it, Dave. I think the idea of doing it at registration time makes sense - you can catch any dependencies that don't exist up front, and also the resolution performance will be heaps better.

If this works on the Silverlight and Compact Framework platforms, Kzu, then it gets my vote to roll into the core. When's the next release?

Coordinator
Jul 7, 2009 at 12:27 PM
I like it too!

One caveat: expression trees is not available on CF and I'm not sure about Silverlight. But for desktop/server, looks very good :)

Can you create a new issue and link to the pastie code?

Thanks!

/kzu

--
Daniel Cazzulino | Developer Lead | XML MVP | Clarius Consulting | +1 425.329.3471

Jul 7, 2009 at 4:10 PM

OK, issue created:

http://funq.codeplex.com/WorkItem/View.aspx?WorkItemId=2158 

Nov 23, 2009 at 1:07 AM

I have what I think is a small improvement to the code for easy registration (from here: http://pastie.org/536675).

Currently the code will look up the constructor on the implementation type with the most parameters and call Resolve() on each of those parameters. However, I found a use case where your implementing type has already been registered with the container. In that scenario, you want Funq to use your existing registration function rather than taking a guess at the constructor to use.

So I have changed the method to look like this:

public static void Register<TService, TImplementation>(this Container container) where TImplementation : TService
{
    Func<Container, TService> exp;

    if (container.IsRegistered<TImplementation>())
    {
        exp = c => c.Resolve<TImplementation>();
    }
    else
    {
        var lambdaParam = Expression.Parameter(typeof(Container), "ref_to_the_container_passed_into_the_lambda");

        var constructorExpression =
            BuildImplConstructorExpression<TImplementation>(lambdaParam);
        exp = CompileInterfaceConstructor<TService>(lambdaParam, constructorExpression);
    }
    container.Register(exp);
}

Essentially this uses my "IsRegistered" method (submitted in a patch) to first check if the implementation is already registered, and if so just call resolve directly on it.

The reason I've found this handy is because I have also added a "named registration" overload to the easy-registration class, and I'm able to do this:

// proper registration code for Foo and Bar
cont.Register<FooImpl>(c => ...);
cont.Register<BarImpl>(c => ...);

// named registration for both
cont.Register<IService, FooImpl>("Foo");
cont.Register<IService, BarImpl>("Bar");

So I have given Funq enough knowledge to properly construct a FooImpl and BarImpl, but I'm relying on "easy registration" to register both of them as an IService by name. Now my "host" class can ask for an IService by name and get the correct implementation. If we didn't check that FooImpl and BarImpl were already registered, Funq would bypass my special registration function and try to create them by resolving their constructor parameters separately.

Does that all make sense?

Nov 23, 2009 at 1:51 AM

Also, according to MSDN, calling Type.GetConstructors() is the same as calling Type.GetConstructors(BindingFlags.Public | BindingFlags.Instance), so there's no need to check for !IsStatic each time. I'll call it like this explicitly just to be sure, but removing the call to !IsStatic will speed it up somewhat.

Ordering by parameter length descending and then asking for the first one (rather than ordering ascending and asking for the last) is a bit quicker too. So here's a faster method fot the class:

    private static ConstructorInfo GetConstructorWithMostParameters<T>()
    {
        return typeof(T)
            .GetConstructors(BindingFlags.Public | BindingFlags.Instance)
            .OrderByDescending(x => x.GetParameters().Length)
            .First();
    }