Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
322 views
in Technique[技术] by (71.8m points)

.net - 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.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...