2010-03-29 18 views
13

Estoy evaluando ninject2 pero no puedo entender cómo hacer la carga diferida a través del kernel.Lazy Loading with Ninject

Por lo que puedo ver, ese tipo de derrotas tiene el propósito de usar los atributos [Inyectar]. ¿Es posible usar InjectAttribute pero obtener carga diferida? Odiaría forzar la construcción completa de un gráfico de objetos cada vez que creara una instancia de un objeto.

Para especificar, realmente siento curiosidad por el rendimiento.

Respuesta

19

Actualización: Mi respuesta original fue escrito antes de que el .NET Framework 4 fue lanzado (junto con Lazy<T>), y la otra respuesta, mientras que un poco más hasta a la fecha, es todavía un poco obsoleto en la actualidad. Dejo mi respuesta original a continuación en caso de que alguien esté atascado en una versión anterior, pero no aconsejaría su uso con la última versión de Ninject o .NET.

El Ninject Factory Extension es la forma moderna de hacer esto. Automáticamente se cableará cualquier argumento o propiedad. No necesita un enlace por separado para esto: simplemente configure sus servicios de la forma habitual y la extensión se encarga del resto.

FYI, la misma extensión también puede conectar interfaces de fábrica personalizadas, o Func<T> argumentos. La diferencia con estos es que crearán una nueva instancia cada vez, por lo que en realidad es una fábrica, no solo instanciación lenta. Simplemente señalando esto porque la documentación no es totalmente clara al respecto.


Como regla general, "la construcción completa de un gráfico de objetos" no debería ser tan caro, y si una clase está siendo inyectado con dependencias que no se puede utilizar, es probablemente una buena señal de que la clase tiene demasiadas responsabilidades.

Esto no es realmente una limitación de Ninject per se: si lo piensas, no es posible tener una "dependencia diferida" a menos que (a) la dependencia que se inyecta sea en sí misma un cargador lento, como la clase Lazy<T> en .NET 4, o (b) todas las propiedades y métodos de la dependencia usan instanciación diferida. Tiene que inyectarse algo allí.

que podría lograr (a) con relativa facilidad mediante el uso de la interfaz de proveedor de una unión método (ed: Ninject no es compatible con los genéricos abiertos con fijaciones proveedor) y vinculantes un tipo genérico abierto. Asumiendo que no tienes.NET 4, que tendría que crear la interfaz y la implementación a sí mismo:

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

A continuación, se puede unir toda la interfaz vago - por lo que sólo se unen las interfaces de forma normal:

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

Y asociar la interfaz perezoso el uso de los genéricos abiertos a un método lambda:

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

Después de eso, se puede introducir vagos argumentos de dependencia/propiedades:

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

Esto no hará que el toda operación perezoso - Ninject todavía tendrá que crear realmente una instancia de la LazyLoader, pero cualquier de segundo nivel dependencias de ISomeService no se cargará hasta MyClass cheques las Value de ese lazyService.

La desventaja obvia es que no implementa ILazy<T>T en sí, por lo MyClass tiene que ser escrito en realidad a aceptar dependencias perezosos si desea que el beneficio de carga diferida. Sin embargo, si es extremadamente costoso crear alguna dependencia particular, esta sería una buena opción. Estoy bastante seguro de que tendrá este problema con cualquier forma de DI, cualquier biblioteca.


Por lo que yo sé, la única manera de hacerlo (b) sin necesidad de escribir código de montañas sería el uso de un generador de proxy como Castle DynamicProxy, o cuando se registra un interceptor dinámico utilizando el Ninject Interception Extension. Esto sería bastante complicado y no creo que quieras seguir esta ruta a menos que sea necesario.

+0

Bueno, ahora estoy confundido. Después de haber pasado la última hora tratando de hacer que esto funcione, estoy leyendo que "ToProvider actualmente no es compatible con los genéricos abiertos". Entonces este enfoque no es válido, por lo que yo sé. Sin embargo, ha sido votado y aceptado como respuesta. – fearofawhackplanet

+0

@fear: Esto puede haber sido un error de mi parte, aunque podría jurar que Ninject 2.x sí lo admite. Si no puede hacer que funcione la versión del proveedor, simplemente conéctese a un método lambda, prácticamente lo mismo, excepto que utiliza la colección 'IContext.GenericArguments' para obtener los params de tipo. – Aaronaught

+0

@Aaronaught: estoy usando Ninject 2.2 y no funcionó para mí. Lo conseguí trabajando usando 'Bind (typeof (ILazy <>)). ToMethod (ctx => (ctx.Kernel.Get (typeof (LazyProvider <>). MakeGenericType (ctx.GenericArguments)) como proveedor de IP). Crear (ctx)); 'que supongo que es lo que quieres decir al vincular a un lambda? Parece estar haciendo el trabajo pero no es muy legible y aparentemente no verifica que los argumentos genéricos sean válidos. Consulta la publicación aquí: http://groups.google.com/group/ninject/browse_thread/thread/b2df51230bed8195?pli=1 – fearofawhackplanet

16

Existe una manera más sencilla de hacer esto:

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

esto funcionó bien para System.Lazy <> –

+0

Además, la propiedad en la clase de uso debe usar Lazy en lugar de T. – liang