Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Autofac parameterless constructor selection

Tags:

autofac

"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

like image 658
kind_robot Avatar asked Dec 21 '12 10:12

kind_robot


3 Answers

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.

like image 87
nemesv Avatar answered Nov 16 '22 21:11

nemesv


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

builder.Register<EmployeeFactory>(c => new EmployeeFactory())
   .As<IEmployeeFactory>()
   .SingleInstance();
like image 40
Jim Bolla Avatar answered Nov 16 '22 21:11

Jim Bolla


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.

like image 2
AFract Avatar answered Nov 16 '22 21:11

AFract