13

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.

Aaronaught
  • 120,909
  • 25
  • 266
  • 342
devlife
  • 15,275
  • 27
  • 77
  • 131

2 Answers2

19

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.

Aaronaught
  • 120,909
  • 25
  • 266
  • 342
  • Well now I'm confused. Having spent the last hour trying to get this to work, I'm reading that "ToProvider currently doesn't support open generics". So this approach is not valid, as far as I can tell. Yet it's been upvoted and accepted as answer. – fearofawhackplanet May 24 '11 at 13:17
  • @fear: This may have been a mistake on my part, although I could swear that Ninject 2.x does support that. If you can't get the provider version to work, then just bind to a lambda method - pretty much the same thing except you use the `IContext.GenericArguments` collection to get the type params. – Aaronaught May 24 '11 at 14:39
  • @Aaronaught: I'm using Ninject 2.2 and it didn't work for me. I got it working using `Bind(typeof(ILazy<>)).ToMethod(ctx => (ctx.Kernel.Get(typeof(LazyProvider<>).MakeGenericType(ctx.GenericArguments)) as IProvider).Create(ctx));` which I presume is what you mean by binding to a lambda? It seems to be doing the job but is not very readable and apparently doesn't check the generic arguments are valid. See post here: http://groups.google.com/group/ninject/browse_thread/thread/b2df51230bed8195?pli=1 – fearofawhackplanet May 25 '11 at 13:29
  • @fear: If you're going to bind to a method then there isn't any point in using the provider - just set up your lambda to call the constructor directly or invoke the `Kernel.Get` method on the target type. (You wanna know something really funny - that thread you linked to was posted by yours truly, and looking at the dates, evidently I had never attempted to use the open generic provider, I just assumed it would work.) – Aaronaught May 25 '11 at 13:39
  • Anyway @fear, you're right about the fact that it doesn't do any type checking, but it really doesn't need to because the `LazyLoader` supports any type. – Aaronaught May 25 '11 at 13:47
  • Hmmm.... I'm not sure I'm quite following this and can't get your update to work. The `LazyProvider` resolves the `Func` param for `LazyLoader`. Using your new method, how do you get the Kernel to resolve `LazyLoader` in the `ToMethod` binding? I need to write something like `Bind(typeof(ILazy<>)).ToMethod(ctx => new LazyLoader(() => ctx.Kernel.Get));` (which won't compile obviously). – fearofawhackplanet May 25 '11 at 15:42
  • @fear: Sorry, I'm not in a position where I can actually test the code at the moment, but I'll try to fix it up when I get home. It's a minor change, you just need to add a `ConstructorArgument` to the binding. – Aaronaught May 25 '11 at 18:54
  • @Aaronaught I'm trying to accomplish the same thing (only with System.Lazy), and I can't seem to get it to work either. You never fixed? – Sir Code-A-Lot Jun 08 '11 at 13:28
  • 2
    I would like to point out that you can now automatically bind lazy types using `ninject.extensions.factory` https://github.com/ninject/ninject.extensions.factory/wiki/Lazy – solidau Jan 31 '14 at 01:22
17

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>());
    }
}
Ed Chapel
  • 6,842
  • 3
  • 30
  • 44