Combining Lazy and convention scanning

Jan 22, 2013 at 12:07 AM
Edited Jan 22, 2013 at 12:10 AM

Hello, I've implemented both the scanning and Lazy functionality that you've prescribed in the documentation, and they work fine.  However, I can't figure out how to combine the two.  Is there a way to create a Lazy registration when scanning the assembly types?  The problem is that to register a Lazy, you really need the generic type so that the following may be created:

 container.Register(() => new Lazy<T>(container.GetInstance<T>));

However, when scanning, I don't have T as a generic type.  I only have the actual type, not the generic parameter.

var registrations =
 from type in assembly.GetExportedTypes()
 where type.GetInterfaces().Length > 0
 select new
 {
     ServiceType = type.GetInterfaces().First(),
     ImplementationType = type
  };

 foreach (var reg in registrations)
 {
   container.Register(reg.ServiceType, reg.ImplementationType);
 }
The only thing I can come up with is:
foreach (var reg in registrations) { var lazyImplementationType = typeof (Lazy<>).MakeGenericType(reg.ImplementationType); container.Register(() => Activator.CreateInstance(lazyImplementationType, container.GetInstance(lazyImplementationType))); container.Register(reg.ServiceType, reg.ImplementationType); }

Seems lame for a couple of reasons, one it's using Activator.CreateInstance  which may defeat the advantage of Lazy. 
Any ideas appreciated! Thanks E

Coordinator
Jan 22, 2013 at 12:04 PM
Edited Jan 22, 2013 at 12:05 PM

It seems to me that you might be overusing lazy initialization. Constructor's should do nothing more than just storing the given dependencies and creation of objects should therefore be very fast. And especially when working with Simple Injector you'll find that even large object graphs are build in no time, which makes the usefulness of Lazy<T> registrations fairly limited. Creating instances that are not used is no crime. On the contrary; the use of Lazy<T> can make the DI configuration harder to verify. Especially mixing registering Lazy<T> instances with batch registration seems like premature optimization.

Nonetheless, you can register lazy instances using the following extension method:

using SimpleInjector.Extensions;

public static class LazyExtensions
{
    public static void RegisterLazy(this Container container, 
        Type serviceType)
    {
        var method = typeof(LazyExtensions).GetMethod("CreateLazy")
            .MakeGenericMethod(serviceType);

        Func<object> lazyInstanceCreator = 
            Expression.Lambda<Func<object>>(
                Expression.Call(method, Expression.Constant(container)))
            .Compile();

        Type lazyServiceType = 
            typeof(Lazy<>).MakeGenericType(serviceType);

        container.Register(lazyServiceType, lazyInstanceCreator);
    }

    public static Lazy<T> CreateLazy<T>(Container container) 
        where T : class
    {
        Func<T> instanceCreator = () => container.GetInstance<T>();
        return new Lazy<T>(instanceCreator);
    }
}

The following two calls do the same:

container.RegisterLazy(typeof(ILogger));

container.Register<Lazy<ILogger>(
    () => new Lazy<ILogger>(() => container.GetInstance<ILogger>()));

I hope this helps.

ps. Perhaps you are interested in this offer: Get Your DI Configuration Checked By The Experts.

Marked as answer by dot_NET_Junkie on 11/4/2013 at 2:05 AM
Jan 22, 2013 at 12:40 PM
Edited Jan 22, 2013 at 12:55 PM

I've also been considering how useful this is with Simple Injector.  What I'm really looking to do is to automatically register both a Lazy and non-lazy versions when scanning.  Then it's just a matter of specifying what I want to be lazy in the constructor without having to do special registration for the lazy items. 

Thanks for the info and input!

E

BTW, in case it helps anybody, here is my final code.  I have some items specified as Lazy in my constructors, some not and it appears to work great.  Thanks again for the help:

    public static class RegistrationExtensions
    {
        public static void RegisterByConvention<T>(this Container container)
        {
            container.RegisterByConvention(typeof (T).Assembly);
        }

        public static void RegisterByConvention(this Container container, Assembly assembly)
        {
            var registrations =
                from type in assembly.GetExportedTypes()
                where type.GetInterfaces().Length > 0
                select new
                    {
                        ServiceType = type.GetInterfaces().First(),
                        ImplementationType = type
                    };

            foreach (var reg in registrations)
            {
                container.Register(reg.ServiceType, reg.ImplementationType);
                container.RegisterLazy(reg.ServiceType);
            }
        }

        public static void RegisterLazy(this Container container, Type serviceType)
        {
            var method = typeof(RegistrationExtensions).GetMethod("CreateLazy").MakeGenericMethod(serviceType);

            Func<object> lazyInstanceCreator =
                Expression.Lambda<Func<object>>(
                Expression.Call(method, Expression.Constant(container)))
                .Compile();

            Type lazyServiceType = typeof(Lazy<>).MakeGenericType(serviceType);

            container.Register(lazyServiceType, lazyInstanceCreator);
        }

        public static Lazy<T> CreateLazy<T>(Container container)where T : class
        {
            return new Lazy<T>(container.GetInstance<T>);
        }
    }