2011-06-21 10 views
14

¿Puede alguien explicar la diferencia entre SatisfyImportsOnce y ComposeParts y por qué uno funcionaría donde el otro no?SatisfyImportsOnce frente a ComposeParts

Específicamente Tengo una aplicación web MVC que estoy usando MEF. A continuación se muestra un código (de esa aplicación) que funciona cuando uso SatisfyImportsOnce pero no lo hace cuando uso ComposeParts. Según entiendo, ComposeParts crea partes que pueden componerse a partir de una matriz de objetos atribuidos y los compone en el contenedor de composición especificado y que SatisfyImportsOnce compone la parte especificada mediante el servicio de composición especificado. Para mi simple cerebro de mono, aunque el inglés es diferente, son semánticamente iguales. Ambos usan CompositionContainer para escupir tipos exportados en los destinos de importación.

public class FormPartCustomatorFactory 
{ 

    [ImportMany(typeof(ICustomRenderer), AllowRecomposition = true)] 
    private readonly List<Lazy<ICustomRenderer, ICustomRendererMetaData>> _rendererImports = new List<Lazy<ICustomRenderer, ICustomRendererMetaData>>(); 

    private readonly Dictionary<string, Lazy<ICustomRenderer, ICustomRendererMetaData>> _renderers; 

    public static readonly FormPartCustomatorFactory Instance = new FormPartCustomatorFactory(); 

    static CompositionContainer _container; 

    private FormPartCustomatorFactory() 
    { 
     using (var catalog = new DirectoryCatalog(HttpRuntime.BinDirectory, "*.dll")) 
     {    
      _container = new CompositionContainer(catalog); 
      _container.SatisfyImportsOnce(this); // <- Works 
      // _container.ComposeParts(this); // DOESN'T Work 
      _renderers = _rendererImports.ToDictionary(q => q.Metadata.Name, q => q); 
     } 
    } 

    ~FormPartCustomatorFactory() 
    { 
     _container.Dispose(); 
    } 

    public static ICustomRenderer Find(string name) 
    { 
     return Instance._renderers[name].Value; 
    } 
} 

Respuesta

20

SatisyImportsOnce compondrán un tipo sin registrarse por recomposición. Por lo tanto, si tiene la intención de utilizar un tipo sin soporte para la recomposición, puede usar SatisfyImportsOnce y hará el trabajo como siempre, pero cualquier cambio en el contenedor (nuevas piezas añadidas o partes eliminadas), entonces su instancia no se ejecutará automáticamente. recomponerse para ofrecer estas nuevas partes.

En la instancia, que está utilizando:

[ImportMany(typeof(ICustomRenderer), AllowRecomposition = true)] 

... pero a través de SatisfyImportsOnce, no se recompone esta importación.

Si no está preocupado por la recomposición, que podría cambiar su inyección de constructor utilice el código, por lo que podría hacer:

[ImportingConstructor] 
public FormPartCustomatorFactory(IEnumerable<Lazy<ICustomRenderer, ICustomRendererMetadata>> renderers) 
{ 
    if (renderers == null) 
     throw new ArgumentNullException("renderers"); 

    _renderers = renderers.ToDictionary(r => r.Metadata.Name, r => r); 
} 

La razón por la que sugeriría la inyección de constructor, es que el conjunto de Lazy<ICustomRenderer, ICustomRendererMetadata> casos son una dependencia explícita que su tipo requiere, por lo que sería mejor instanciar su tipo en un estado utilizable, en lugar de crear instancias y luego requerir un paso adicional para que esté listo para su primer uso.

Esto hace que tu tipo FormPartCustomatorFactory sea mucho más susceptible de prueba. Con este fin, si tuviera que cambiar el constructor como tal, su método para convertirlo en un singleton no funcionaría. En su lugar, se puede aprovechar la funcionalidad de gestión de vida del MEF, por lo que posiblemente rediseñar su tipo como:

public interface IFormPartCustomatorFactory 
{ 
    ICustomRenderer Find(string name); 
} 

[Export(typeof(IFormPartCustomerFactory)), PartCreationPolicy(CreationPolicy.Shared)] 
public class FormPartCustomatorFactory : IFormPartCustomatorFactory 
{ 
    private IEnumerable<Lazy<ICustomRenderer, ICustomRendereMetadata>> _renderers; 

    [ImportingConstructor] 
    public FormPartCustomatorFactory(IEnumerable<Lazy<ICustomRenderer, ICustomRendererMetadata>> renderers) 
    { 
     if (renderers == null) 
      throw new ArgumentNullException("renderers"); 

     _renderers = renderers; 
    } 

    public ICustomRenderer Find(string name) 
    { 
     return _renderers 
      .Where(r => r.Metadata.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase) 
      .Select(r => r.Value) 
      .FirstOrDefault(); 
    } 
} 

Hacerlo de esta manera significa que su tipo no depende del MEF, que puede ser utilizado sin ella, su más comprobable, y el CompositionContainer administrará la vida útil de la pieza, en este caso el CreationPolicy.Shared (que es el predeterminado para los tipos exportados), utiliza una estrategia de duración única. Luego puede importar una instancia de IFormPartCustomator, importa la misma instancia de singleton.

También diría que llamarlo Factory es posiblemente incorrecto, ya que una fábrica está diseñada para crear nuevas instancias, mientras que, su tipo solo creará una instancia de cada ICustomRenderer. Si este es el comportamiento previsto, tal vez sería mejor llamar a FormPartCustomatorService, que implementa una interfaz IFormPartCusomatorService? Si desea crear nuevas instancias cada vez, puede mirar ExportFactory<ICustomRenderer, ICustomRendererMetadata>.

+0

Gracias, esta es una excelente respuesta. – Peter

9

Como menciona Matthew, SatisfyImportsOnce no registra la pieza para la recomposición. Esto significa que el contenedor MEF no contiene una referencia a la parte.

Además, SatisfyImportsOnce solo satisface las importaciones de una pieza, pero ignora las exportaciones que tiene. ComposeParts agregaría las exportaciones al contenedor también.

+0

Gracias Daniel. muy útil – Peter

+2

"ComposeParts agregaría las exportaciones al contenedor también". - esto era lo que estaba buscando :) –