2010-03-29 18 views
13

Ich werte ninject2 aus, kann aber nicht herausfinden, wie man lazy loading anders als über den Kernel macht.Lazy Loading with Ninject

Von dem, was ich sehen kann, besiegt diese Art der Zweck der Verwendung der Attribute [Inject]. Ist es möglich, das InjectAttribute zu verwenden, aber lazy loading? Ich würde es hassen, jedes Mal, wenn ich ein Objekt instanziiere, die vollständige Konstruktion eines Objektgraphen zu erzwingen.

Um zu spezifizieren, bin ich wirklich nur neugierig auf die Leistung.

Antwort

19

Update: Meine ursprüngliche Antwort wurde geschrieben, bevor das .NET Framework 4 veröffentlicht wurde (zusammen mit Lazy<T>), und der anderen Antwort, während etwas mehr up-to-date, ist immer noch ein bisschen jetzt veraltet. Ich lasse meine ursprüngliche Antwort unten, für den Fall, dass jemand an einer älteren Version festhängt, würde aber seine Verwendung mit der neuesten Version von Ninject oder .NET nicht empfehlen.

Das Ninject Factory Extension ist die moderne Art, dies zu tun. Es verbindet automatisch alle Argumente oder Eigenschaften. Sie benötigen hierfür keine separate Bindung - richten Sie Ihre Dienste einfach wie gewohnt ein und die Erweiterung erledigt den Rest.

FYI, die gleiche Erweiterung kann auch benutzerdefinierte Factory-Schnittstellen oder Func<T> Argumente verdrahten. Der Unterschied zu diesen ist, dass sie jedes Mal eine neue Instanz erstellen, also ist es eigentlich eine Fabrik, nicht nur eine faule Instanziierung. Ich zeige das nur, weil die Dokumentation nicht ganz klar ist.


In der Regel „komplette Konstruktion eines Objekt Graph“ sollte nicht so teuer sein, und wenn eine Klasse mit Abhängigkeiten eingespritzt wird, dass es nicht verwendet werden kann, ist es wahrscheinlich ein gutes Zeichen, dass die Klasse hat zu viele Verantwortlichkeiten.

Dies ist nicht wirklich eine Einschränkung von Ninject per se - wenn Sie darüber nachdenken, ist es nicht wirklich möglich, eine "faule Abhängigkeit" zu haben, es sei denn, entweder (a) die Abhängigkeit injiziert ist selbst ein fauler Lader, wie Die Lazy<T> Klasse in .NET 4, oder (b) alle Eigenschaften und Methoden der Abhängigkeit verwenden Lazy Instanziierung. Irgendwas muss dort hinein gespritzt werden.

Sie könnten (a) erreichen relativ leicht durch der Provider-Schnittstelle ein Verfahren verbindlich mit (ed: Ninject nicht offen Generika unterstützt mit Provider-Bindungen) und einen offenen generischen Typ zu binden. Angenommen, Sie haben es nicht.NET 4: Halten Sie die Schnittstelle schaffen haben und Implementierung selbst:

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

Dann können Sie die gesamte faul Schnittstelle binden - so binden nur die Schnittstellen als normal:

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

Und die faul Schnittstelle binden mit offenen Generika zu einer Lambda-Methode:

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

Danach können Sie faul Abhängigkeit Argumente/Eigenschaften vorstellen:

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

Dies wird nicht machen die gesamte Betrieb faul - Ninject noch tatsächlich müssen eine Instanz der LazyLoader erstellen, aber alle Second-Level- Abhängigkeiten von ISomeService erst MyClass Kontrollen geladen werden, um die Value davon lazyService.

Der offensichtliche Nachteil ist, dass ILazy<T>T sich nicht selbst implementiert, so muss MyClass eigentlich geschrieben werden, um faule Abhängigkeiten zu akzeptieren, wenn Sie den Nutzen des faulen Ladens wollen. Wenn es jedoch extrem teuer ist, bestimmte Abhängigkeiten zu erzeugen, wäre dies eine gute Option. Ich bin mir ziemlich sicher, dass Sie dieses Problem mit jeder Form von DI, jede Bibliothek haben.


Soweit ich weiß, zu tun, der einzige Weg, (b) ohne Berge Code zu schreiben wäre, einen Proxy-Generator wie Castle DynamicProxy, zu verwenden oder eine dynamische Interceptor registrieren Ninject Interception Extension verwenden. Das wäre ziemlich kompliziert und ich denke nicht, dass du diesen Weg gehen willst, wenn du nicht musst.

+0

Nun ich bin verwirrt. Ich habe gelesen, dass "ToProvider derzeit offene Generika nicht unterstützt", nachdem ich die letzte Stunde damit verbracht habe, dies zur Arbeit zu bringen. Dieser Ansatz ist also nicht gültig, soweit ich das beurteilen kann. Dennoch wurde es aufgewertet und als Antwort akzeptiert. – fearofawhackplanet

+0

@fear: Das könnte ein Fehler meinerseits gewesen sein, obwohl ich schwören könnte, dass Ninject 2.x das unterstützt. Wenn Sie die Provider-Version nicht zum Laufen bringen können, dann binden Sie einfach an eine Lambda-Methode - so ziemlich das Gleiche, außer dass Sie die IContext.GenericArguments-Sammlung verwenden, um die Typ-Parameter zu erhalten. – Aaronaught

+0

@Aaronaught: Ich benutze Ninject 2.2 und es hat nicht für mich funktioniert. Ich habe es funktioniert mit 'Bind (typeof (ILazy <>)). ToMethod (ctx => (ctx.Kernel.Get (typeof (LazyProvider <>). MakeGenericType (ctx.GenericArguments)) als IProvider). Erstellen (ctx)), was ich vermute, ist was du meinst, indem du an ein Lambda bindest? Es scheint den Job zu erledigen, ist aber nicht gut lesbar und überprüft offensichtlich nicht, ob die generischen Argumente gültig sind. Siehe post hier: http://groups.google.com/group/ninject/browse_thread/thread/b2df51230bed8195?pli=1 – fearofawhackplanet

16

Es ist ein einfacher Weg, dies zu tun:

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

das hat gut für System.Lazy funktioniert <> –

+0

Außerdem sollte die Eigenschaft in der Verwendungsklasse Lazy anstelle von T verwenden. – liang