【发布时间】:2011-02-02 01:08:01
【问题描述】:
我正在评估 ninject2,但似乎无法弄清楚除了通过内核之外如何进行延迟加载。
据我所知,这违背了使用 [Inject] 属性的目的。 是否可以使用 InjectAttribute 但延迟加载?每次实例化一个对象时,我都不想强制构建一个对象图。
具体来说,我真的只是对性能感到好奇。
【问题讨论】:
标签: .net lazy-loading ninject
我正在评估 ninject2,但似乎无法弄清楚除了通过内核之外如何进行延迟加载。
据我所知,这违背了使用 [Inject] 属性的目的。 是否可以使用 InjectAttribute 但延迟加载?每次实例化一个对象时,我都不想强制构建一个对象图。
具体来说,我真的只是对性能感到好奇。
【问题讨论】:
标签: .net lazy-loading ninject
更新:我的原始答案是在 .NET Framework 4 发布之前编写的(连同Lazy<T>),而另一个答案虽然更新了一些,但仍然是现在有点过时了。我将在下面留下我原来的答案,以防有人卡在旧版本上,但不建议将其与最新版本的 Ninject 或 .NET 一起使用。
Ninject Factory Extension 是执行此操作的现代方式。它会自动连接任何参数或属性。您不需要为此单独绑定 - 只需以通常的方式设置您的服务,然后扩展处理其余部分。
仅供参考,同样的扩展也可以连接自定义工厂接口,或Func<T> 参数。与这些不同的是,它们每次都会创建一个新实例,因此它实际上是一个工厂,而不仅仅是惰性实例化。只是指出这一点,因为文档并不完全清楚。
作为一项规则,“对象图的完整构建”不应该那么昂贵,如果一个类被注入了它可能不会使用的依赖项,这可能是一个好兆头,表明该类有太多的责任。
这并不是 Ninject 本身的真正限制——如果你仔细想想,它实际上不可能有一个“惰性依赖”,除非 (a) 被注入的依赖本身就是一个惰性加载器,例如Lazy<T> .NET 4 中的类,或 (b) 所有依赖项的属性和方法都使用延迟实例化。 一些东西必须注入那里。
您可以通过使用提供者接口方法绑定(ed: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 的实例,但 @ 的任何 二级 依赖项在MyClass 检查Value 的lazyService 之前,不会加载987654335@。
明显的缺点是ILazy<T> 本身并没有实现T,所以如果你想要延迟加载的好处,就必须实际编写MyClass 以接受延迟依赖。然而,如果创建某些特定依赖项的成本非常高,这将是一个不错的选择。我很确定您会在任何形式的 DI、任何库中遇到这个问题。
据我所知,在不编写大量代码的情况下执行 (b) 的唯一方法是使用像 Castle DynamicProxy 这样的代理生成器,或者使用 Ninject Interception Extension 注册动态拦截器。这会很复杂,我认为你不会想走这条路,除非你必须这样做。
【讨论】:
IContext.GenericArguments 集合获取类型参数。
Bind(typeof(ILazy<>)).ToMethod(ctx => (ctx.Kernel.Get(typeof(LazyProvider<>).MakeGenericType(ctx.GenericArguments)) as IProvider).Create(ctx)); 让它工作,我认为这就是绑定到 lambda 的意思?它似乎在做这项工作,但不是很易读,显然没有检查通用参数是否有效。请参阅此处的帖子:groups.google.com/group/ninject/browse_thread/thread/…
Kernel.Get 方法在目标类型上。 (你想知道一些非常有趣的事情——你链接到的那个帖子是你真正发布的,看看日期,显然我从来没有尝试过使用开放的通用提供程序,我只是认为它会起作用。)
ninject.extensions.factorygithub.com/ninject/ninject.extensions.factory/wiki/Lazy自动绑定惰性类型
有一个更简单的方法来做到这一点:
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>());
}
}
【讨论】: