views:

231

answers:

1

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.

+6  A: 

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 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;
        }
    }
}

The Ninject provider would look something like this:

public class LazyProvider<T> : Provider<ILazy<T>>
{
    public override T CreateInstance(IContext context)
    {
        return new LazyLoader<T>(() => context.Kernel.Get<T>());
    }
}

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 provider to the open generic type:

Bind(typeof(ILazy<>)).ToProvider(typeof(LazyProvider<>));

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 LazyProvider and subsequently 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