2012-12-21 8 views
8

"Autofac sceglie automaticamente il costruttore con il maggior numero di parametri che è possibile ottenere dal contenitore." Voglio farlo diversamente e scegliere invece il costruttore predefinito. http://code.google.com/p/autofac/wiki/AutowiringSelezione costruttore senza parametri Autofac

internal class ParameterlessConstructorSelector : IConstructorSelector 
{ 
    #region Implementation of IConstructorSelector 

    /// <summary> 
    /// Selects the best constructor from the available constructors. 
    /// </summary> 
    /// <param name="constructorBindings">Available constructors.</param> 
    /// <returns> 
    /// The best constructor. 
    /// </returns> 
    public ConstructorParameterBinding SelectConstructorBinding(ConstructorParameterBinding[] constructorBindings) 
    { 
     return constructorBindings.First(); 
    } 

    #endregion 
} 

Quando possibile cablare la classe, ho fatto questo:

builder.RegisterType<EmployeeFactory>() 
     .As<IEmployeeFactory>().UsingConstructor(new ParameterlessConstructorSelector()) 
     .SingleInstance(); 

Il primo legame nella lista constructorBindings è sempre quello con il costruttore paremeterless. Non sono sicuro se è stato definito prima o il modo in cui autofac esegue la scansione dei costruttori ma è questo l'approccio giusto per il wire per il costruttore senza parametri?

Grazie

risposta

6

Autofac usa internamente il metodo Type.GetConstructors per scoprire i costruttori.

Dai metodi documentation:

Procedimento GetConstructors non restituisce costruttori in un ordine particolare , come ordine di dichiarazione. Il tuo codice non deve dipendere dallo nell'ordine in cui vengono restituiti i costruttori, perché l'ordine varia.

Quindi è stata solo fortuna che ha funzionato con il First() nel tuo caso. In una corretta applicazione è necessario cercare in modo esplicito per il costruttore con 0 argomenti:

public class DefaultConstructorSelector : IConstructorSelector 
{ 
    public ConstructorParameterBinding SelectConstructorBinding(
     ConstructorParameterBinding[] constructorBindings) 
    { 
     var defaultConstructor = constructorBindings 
      .SingleOrDefault(c => c.TargetConstructor.GetParameters().Length == 0); 
     if (defaultConstructor == null) 
      //handle the case when there is no default constructor 
      throw new InvalidOperationException();     
     return defaultConstructor; 
    } 
} 

è possibile testare la teoria, con questa classe molto semplice:

public class MyClass 
{ 
    public readonly int i; 

    public MyClass(int i) 
    { 
     this.i = i; 
    } 

    public MyClass() 
    { 
     i = 1; 
    } 
} 

Con l'implementazione:

var builder = new ContainerBuilder(); 
// register 22 for each integer constructor argument 
builder.Register<int>(v => 22); 

builder.RegisterType<MyClass>().AsSelf() 
    .UsingConstructor(new ParameterlessConstructorSelector()); 
var c = builder.Build(); 
var myClass = c.Resolve<MyClass>(); 
Console.WriteLine(myClass.i); 

Emette 22 ad es. Il costruttore con l'argomento int viene chiamato:

Con la mia realizzazione:

//... 
builder.RegisterType<MyClass>().AsSelf() 
    .UsingConstructor(new DefaultConstructorSelector()); 
//... 
var myClass = c.Resolve<MyClass>(); 
Console.WriteLine(myClass.i); 

Esso emette 1 per esempio il costruttore di default è chiamato.

+0

Sapevo di essere stato fortunato con First() :) Grazie per la spiegazione dettagliata. –

+0

Invece di 'InvalidOperationException', Autofac ora ha una classe' DependencyResolutionException' che è più appropriata se il codificatore senza parametri non viene trovato. – aholmes

1

Non sarebbe più semplice registrare solo in modo esplicito il costruttore predefinito?

builder.Register<EmployeeFactory>(c => new EmployeeFactory()) 
    .As<IEmployeeFactory>() 
    .SingleInstance(); 
+1

Sì, potrebbe funzionare per questo semplice caso. Ma se si registrano più tipi contemporaneamente (ad esempio con 'RegisterAssemblyTypes') l'implementazione di un personalizzato' IConstructorSelector' è la soluzione più pulita. – nemesv

+5

Le ultime versioni di autofac hanno overload Using paramonstr (tipo Parametri []) che seleziona il costruttore con i tipi appropriati - se invocato senza parametri, selezionerà il costruttore predefinito. – Alexander

1

con le versioni recenti di Autofac, questo è molto semplice:

builder.RegisterType<EmployeeFactory>() 
     .As<IEmployeeFactory>().UsingConstructor() 
     .SingleInstance(); 

Calling "UsingConstructor" senza parametri significa "usare costruttore senza parametri". Vedere https://autofac.org/apidoc/html/EB67DEC4.htm e pagine correlate.