Search code examples
c#.netreflectionlazy-loadingsimple-injector

SimpleInjector Lazy in a Reflection


We are using SimpleInjector as a Dependency Injector, and we are registering all interface types using assembly iteration.

public static void RegisterInterfaceTypes(this Container container, Assembly assembly)
{
    assembly.GetExportedTypes()
        .Select(t => new {
            Type = t,
            Interface = t.GetInterfaces().FirstOrDefault()
        })
        .ToList()
        .ForEach(t =>
        {
            container.Register(t.Interface, t.Type, Lifestyle.Transient);
        });
}

We also have lazy classes to register. We can register these classes like below one by one. But we want to register all lazy types with similar iteration using reflection.

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

Solution

  • You can make use of the ResolveUnregisteredType extension method to make last-minute registrations for resolve Lazy<T> dependencies:

    Source:

    public static void AllowResolvingLazyFactories(this Container container)
    {
        container.ResolveUnregisteredType += (sender, e) =>
        {
            if (e.UnregisteredServiceType.IsGenericType &&
                e.UnregisteredServiceType.GetGenericTypeDefinition() == typeof(Lazy<>))
            {
                Type serviceType = e.UnregisteredServiceType.GetGenericArguments()[0];
    
                InstanceProducer registration = container.GetRegistration(serviceType, true);
    
                Type funcType = typeof(Func<>).MakeGenericType(serviceType);
                Type lazyType = typeof(Lazy<>).MakeGenericType(serviceType);
    
                var factoryDelegate = Expression.Lambda(funcType, registration.BuildExpression()).Compile();
    
                var lazyConstructor = (
                    from ctor in lazyType.GetConstructors()
                    where ctor.GetParameters().Length == 1
                    where ctor.GetParameters()[0].ParameterType == funcType
                    select ctor)
                    .Single();
    
                var expression = Expression.New(lazyConstructor, Expression.Constant(factoryDelegate));
    
                var lazyRegistration = registration.Lifestyle.CreateRegistration(
                    serviceType: lazyType,
                    instanceCreator: Expression.Lambda<Func<object>>(expression).Compile(),
                    container: container);
    
                e.Register(lazyRegistration);
            }
        };
    }
    

    Usage:

    container.AllowResolvingLazyFactories();
    

    But please note the warnings from the documentation:

    Warning: Registering [Lazy<T>] by default is a design smell. The use of [Lazy<T>] makes your design harder to follow and your system harder to maintain and test. Your system should only have a few of those [...] at most. If you have many constructors in your system that depend on a [Lazy<T>], please take a good look at your dependency strategy. The following article goes into details about why [this is] a design smell.

    Warning: [...] the constructors of your components should be simple, reliable and quick (as explained in this blog post by Mark Seemann). That would remove the need for lazy initialization. For more information about creating an application and container configuration that can be successfully verified, please read the How To Verify the container’s configuration.