2010-03-29 144 views
13

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

从我所看到的那种失败了使用[注入]属性的目的。 是否可以使用InjectAttribute,但获得延迟加载?每次我实例化一个对象时,我都不想强制完成对象图的构造。

要指定,我真的只是好奇的表现。

回答

19

更新:我原来的答复是.NET Framework 4的发布(连同Lazy<T>)之前写的,对方的回答,而稍微跟上时代的,现在仍然是一个有点过时。我在下面留下我的原始答案,以防有人被困在旧版本中,但不会建议使用最新版本的Ninject或.NET。

Ninject Factory Extension是这样做的现代方式。它会自动连接任何参数或属性。您不需要单独的绑定 - 只需按照常规方式设置您的服务,然后扩展就可以处理剩下的事情。

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


作为一项规则,“对象图的完整建筑”不应该是昂贵的,如果一个类被与依赖注入,它可能无法使用,它可能是一个很好的迹象,表明类有责任太多。如果你仔细想想,除非(a)被注入的依赖本身就是一个惰性的加载器,比如说,它并不是真的可能存在“惰性依赖” .NET 4中的Lazy<T>类,或(b)所有依赖项的属性和方法都使用延迟实例化。 东西必须注入那里。

你可以完成(一)相对容易地通过使用提供商接口的方法结合(编辑:Ninject不支持与提供商绑定开放仿制药)和结合的开放式泛型类型。假设你没有。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>(); 

,并绑定懒接口使用开放式泛型拉姆达方法:

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检查那lazyServiceValue

明显的缺点是ILazy<T>本身没有实现T,因此MyClass必须写入接受懒惰依赖关系,如果你想延迟加载的好处。不过,如果创建某种特定的依赖关系的代价非常昂贵,这将是一个不错的选择。我很肯定你会在任何形式的DI,任何库上遇到这个问题。


据我所知,只有这样,才能做到(B),而无需编写代码的山会使用代理生成像Castle DynamicProxy,或使用Ninject Interception Extension注册一个动态的拦截器。这将是非常复杂的,我不认为你会想要去这条路线,除非你必须。

+0

现在好了,我很困惑。花了最后一小时试图让这个工作,我读到“ToProvider目前不支持开放式泛型”。就我所知,这种方法是无效的。但它已被提升并被接受为答案。 – fearofawhackplanet 2011-05-24 13:17:03

+0

@fear:这可能是我的错,尽管我可以发誓Ninject 2.x确实支持这一点。如果无法让提供者版本正常工作,那么只需绑定到lambda方法 - 除了使用'IContext.GenericArguments'集合来获取类型参数外,几乎完全相同。 – Aaronaught 2011-05-24 14:39:53

+0

@Aaronaught:我使用的是Ninject 2.2,它不适合我。我使用了'Bind(typeof(ILazy <>))。ToMethod(ctx =>(ctx.Kernel.Get(typeof(LazyProvider <>)。MakeGenericType(ctx.GenericArguments))作为IProvider).Create(ctx) );'我认为你绑定到lambda是什么意思?它似乎在做这项工作,但不太可读,显然不检查通用参数是否有效。在这里看到帖子:http://groups.google.com/group/ninject/browse_thread/thread/b2df51230bed8195?pli=1 – fearofawhackplanet 2011-05-25 13:29:29

16

还有就是做这个简单的方法:

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

这对于System.Lazy <> – 2013-09-26 21:07:10

+0

另外,使用类中的属性应该使用Lazy 而不是T. – liang 2014-01-09 08:43:34