Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lazy Loading with Ninject

I'm evaluating ninject2 but can't seem to figure out how to do lazy loading other than through the kernel.

From what I can see that kind of defeats the purpose of using the [Inject] attributes. Is it possible to use the InjectAttribute but get lazy loading? I'd hate to force complete construction of an object graph every time I instantiated an object.

To specify, I'm really just curious about the performance.

like image 314
devlife Avatar asked Mar 29 '10 13:03

devlife


2 Answers

There is a simpler way to do this:

public class Module : NinjectModule
{
    public override void Load()
    {
        Bind(typeof(Lazy<>)).ToMethod(ctx => 
                GetType()
                    .GetMethod("GetLazyProvider", BindingFlags.Instance | BindingFlags.NonPublic)
                    .MakeGenericMethod(ctx.GenericArguments[0])
                    .Invoke(this, new object[] { ctx.Kernel }));
    }

    protected Lazy<T> GetLazyProvider<T>(IKernel kernel)
    {
        return new Lazy<T>(() => kernel.Get<T>());
    }
}
like image 142
Ed Chapel Avatar answered Nov 02 '22 01:11

Ed Chapel


Update: My original answer was written before the .NET Framework 4 was released (along with Lazy<T>), and the other answer, while slightly more up-to-date, is still a bit obsolete now. I'm leaving my original answer below in case anybody is stuck on an older version, but wouldn't advise its use with the latest version of Ninject or .NET.

The Ninject Factory Extension is the modern way of doing this. It will automatically wire up any arguments or properties. You don't need a separate binding for this - just set up your services in the usual way and the extension handles the rest.

FYI, the same extension can also wire up custom factory interfaces, or Func<T> arguments. The difference with these is that they'll create a new instance every time, so it's actually a factory, not just lazy instantiation. Just pointing this because the documentation isn't totally clear about it.


As a rule, "complete construction of an object graph" shouldn't be that expensive, and if a class is being injected with dependencies that it may not use, it's probably a good sign that the class has too many responsibilities.

This isn't really a limitation of Ninject per se - if you think about it, it's not really possible to have a "lazy dependency" unless either (a) the dependency being injected is itself a lazy loader, such as the Lazy<T> class in .NET 4, or (b) all of the dependency's properties and methods use lazy instantiation. Something has to be injected in there.

You could accomplish (a) relatively easily by using the provider interface a method binding (ed: Ninject doesn't support open generics with provider bindings) and binding an open generic type. Assuming you don't have .NET 4, you would have to create the interface and implementation yourself:

public interface ILazy<T>
{
    T Value { get; }
}

public class LazyLoader<T> : ILazy<T>
{
    private bool isLoaded = false;
    private T instance;
    private Func<T> loader;

    public LazyLoader(Func<T> loader)
    {
        if (loader == null)
            throw new ArgumentNullException("loader");
        this.loader = loader;
    }

    public T Value
    {
        get
        {
            if (!isLoaded)
            {
                instance = loader();
                isLoaded = true;
            }
            return instance;
        }
    }
}

Then you can bind the entire lazy interface - so just bind the interfaces as normal:

Bind<ISomeService>().To<SomeService>();
Bind<IOtherService>().To<OtherService>();

And bind the lazy interface using open generics to a lambda method:

Bind(typeof(ILazy<>)).ToMethod(ctx =>
{
    var targetType = typeof(LazyLoader<>).MakeGenericType(ctx.GenericArguments);
    return ctx.Kernel.Get(targetType);
});

After that, you can introduce lazy dependency arguments/properties:

public class MyClass
{
    [Inject]
    public MyClass(ILazy<ISomeService> lazyService) { ... }
}

This won't make the entire operation lazy - Ninject will still have to actually create an instance of the LazyLoader, but any second-level dependencies of ISomeService won't be loaded until MyClass checks the Value of that lazyService.

The obvious downside is that ILazy<T> does not implement T itself, so MyClass has to actually be written to accept lazy dependencies if you want the benefit of lazy loading. Nevertheless, if it's extremely expensive to create some particular dependency, this would be a good option. I'm pretty sure you'll have this issue with any form of DI, any library.


As far as I know, the only way to do (b) without writing mountains of code would be to use a proxy generator like Castle DynamicProxy, or register a dynamic interceptor using the Ninject Interception Extension. This would be pretty complicated and I don't think you'd want to go this route unless you have to.

like image 30
Aaronaught Avatar answered Nov 02 '22 00:11

Aaronaught