2010-03-29 192 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