kind_robot
kind_robot

Reputation: 2523

Autofac parameterless constructor selection

"Autofac automatically chooses the constructor with the most parameters that are able to be obtained from the container." I want it to do otherwise and choose the default constructor instead. http://code.google.com/p/autofac/wiki/Autowiring

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
}

When I wire the class, I did this:

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

The first binding in the constructorBindings list is always the one with paremeterless constructor. Not sure if it defined first or the way autofac scans the constructors but is this the right approach to wire for parameterless constructor?

Thanks

Upvotes: 10

Views: 6607

Answers (3)

AFract
AFract

Reputation: 9680

With recent versions of Autofac, this is very simple :

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

Calling "UsingConstructor" with no parameters means "use parameterless constructor". See https://autofac.org/apidoc/html/EB67DEC4.htm and related pages.

Upvotes: 3

Jim Bolla
Jim Bolla

Reputation: 8295

Wouldn't it be simpler to just explicitly register the default constructor?

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

Upvotes: 3

nemesv
nemesv

Reputation: 139758

Autofac internally uses the Type.GetConstructors method to discover the constructors.

From the methods documentation:

The GetConstructors method does not return constructors in a particular order, such as declaration order. Your code must not depend on the order in which constructors are returned, because that order varies.

So it was just luck that it worked with the First() in your case. In a proper implementation you need to explicitly search for the constructor with 0 arguments:

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

You can test the theory with this very simple class:

public class MyClass
{
    public readonly int i;

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

    public MyClass()
    {
        i = 1;
    }
}

With your implementation:

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

It outputs 22 e.g the constructor with the int argument is called:

With my implementation:

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

It outputs 1 e.g the default constructor is called.

Upvotes: 7

Related Questions