我正在评估ninject2,但似乎无法弄清楚如何通过内核进行延迟加载。
从我所看到的那种方法来说,失败了使用[Inject]属性的目的。 是否可以使用InjectAttribute但延迟加载?每次我实例化一个对象时,我都讨厌强制完整构建一个对象图。
要说明,我真的只是对表现感到好奇。
答案 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>());
}
}