使用Ninject进行延迟加载

时间:2010-03-29 13:19:51

标签: .net lazy-loading ninject

我正在评估ninject2,但似乎无法弄清楚如何通过内核进行延迟加载。

从我所看到的那种方法来说,失败了使用[Inject]属性的目的。 是否可以使用InjectAttribute但延迟加载?每次我实例化一个对象时,我都讨厌强制完整构建一个对象图。

要说明,我真的只是对表现感到好奇。

2 个答案:

答案 0 :(得分:19)

更新:我的原始答案是在.NET Framework 4发布之前编写的(与Lazy<T>一起),而另一个答案,虽然稍微更新,但是现在还有点过时了。我将在下面留下我原来的答案,以防任何人被困在旧版本上,但不会建议使用最新版本的Ninject或.NET。

Ninject Factory Extension是现代的做法。它会自动连接任何参数或属性。您不需要单独的绑定 - 只需以通常的方式设置您的服务,扩展程序处理其余的服务。

仅供参考,相同的扩展也可以连接自定义工厂接口或Func<T>参数。与它们的不同之处在于它们每次都会创建一个新实例,所以它实际上是一个工厂,而不仅仅是懒惰的实例化。只是指出这一点,因为文档并不完全清楚。


作为一项规则,“对象图的完整构造”不应该那么昂贵,并且如果一个类被注入了它可能不使用的依赖项,那么这可能是一个好的迹象,表明该类有太多的责任。

这并不是Ninject本身的限制 - 如果你考虑它,除非(a)注入的依赖本身是一个懒惰的加载器,例如.NET 4中的Lazy<T>类,或(b)所有依赖项的属性和方法都使用延迟实例化。 必须在那里注入一些

通过使用提供程序接口方法绑定(编辑:Ninject不支持使用提供程序绑定的开放式泛型)并绑定,可以相对轻松地完成(a)打开通用类型。假设您没有.NET 4,则必须自己创建接口和实现:

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

然后你可以绑定整个懒惰的接口 - 所以只需正常绑定接口:

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

使用开放泛型将惰性接口绑定到lambda方法:

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

之后,您可以引入延迟依赖性参数/属性:

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

这不会使整个操作变得懒惰--Ninject仍然必须实际创建LazyLoader的实例,但任何第二级依赖项在ISomeService检查MyClass的{​​{1}}之前,系统不会加载Value

明显的缺点是lazyService本身没有实现ILazy<T>,因此如果你想要延迟加载的好处,必须实际编写T来接受延迟依赖。然而,如果创建一些特定的依赖是非常昂贵的,这将是一个很好的选择。我很确定你会在任何形式的DI,任何库中遇到这个问题。


据我所知,(b)没有编写代码的唯一方法是使用像Castle DynamicProxy这样的代理生成器,或使用Ninject Interception Extension注册动态拦截器。这将非常复杂,除非你必须,否则我认为你不想走这条路。

答案 1 :(得分:17)

有一种更简单的方法可以做到这一点:

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