castleproject / Windsor

Castle Windsor is a best of breed, mature Inversion of Control container available for .NET
http://www.castleproject.org
Apache License 2.0
1.52k stars 455 forks source link

DynamicParameters stops working if a dependent component has constructor overloads #523

Open jnm2 opened 4 years ago

jnm2 commented 4 years ago

See the description in the comments:

using Castle.MicroKernel.Registration;
using Castle.Windsor;

public static class Program
{
    public static void Main()
    {
        var container = new WindsorContainer().Register(
            Component.For<ReproComponent>(),
            Component.For<ContainerDependency1>(),
            Component.For<ContainerDependency2>()
                .DynamicParameters((k, p) => p["userId"] = 42));

        // Throws HandlerException *only* if there is more than one ReproComponent constructor
        // overload:
        // > Can't create component 'Program+ReproComponent' as it has dependencies to be
        // > satisfied.
        // > 'Program+ReproComponent' is waiting for the following dependencies:
        // > -Service 'Program+ContainerDependency2' which was registered but is also waiting
        // > for dependencies.
        // > 'Program+ContainerDependency2' is waiting for the following dependencies:
        // > -Parameter 'userId' which was not provided.Did you forget to set the dependency ?
        // > -Service 'Program+NonContainerDependency' which was not registered.
        container.Resolve<ReproComponent>();
    }

    public sealed class ReproComponent
    {
        public ReproComponent(ContainerDependency1 dependency1, ContainerDependency2 dependency2) { }

        // If you comment out this line, the error disappears.
        public ReproComponent(NonContainerDependency dependency1, ContainerDependency2 dependency2) { }
    }

    public sealed class ContainerDependency1
    {
    }

    public sealed class ContainerDependency2
    {
        public ContainerDependency2(int userId) { }
    }

    public sealed class NonContainerDependency
    {
    }
}
jonorossi commented 4 years ago

Did you want to debug DefaultComponentActivator to determine why the 2nd constructor is getting a higher score even though it has a non-satisfiable dependency.

jnm2 commented 4 years ago

I put a breakpoint in every method in DefaultComponentActivator and none was hit except the constructor. Specifically, none of the scoring logic in DefaultComponentActivator is in play for this repro. This is the call stack:

   at Castle.MicroKernel.Handlers.DefaultHandler.AssertNotWaitingForDependency() in C:\Users\Joseph\Source\Repos\Windsor\src\Castle.Windsor\MicroKernel\Handlers\DefaultHandler.cs:line 74
   at Castle.MicroKernel.Handlers.DefaultHandler.ResolveCore(CreationContext context, Boolean requiresDecommission, Boolean instanceRequired, Burden& burden) in C:\Users\Joseph\Source\Repos\Windsor\src\Castle.Windsor\MicroKernel\Handlers\DefaultHandler.cs:line 146
   at Castle.MicroKernel.Handlers.DefaultHandler.Resolve(CreationContext context, Boolean instanceRequired) in C:\Users\Joseph\Source\Repos\Windsor\src\Castle.Windsor\MicroKernel\Handlers\DefaultHandler.cs:line 97
   at Castle.MicroKernel.Handlers.AbstractHandler.Resolve(CreationContext context) in C:\Users\Joseph\Source\Repos\Windsor\src\Castle.Windsor\MicroKernel\Handlers\AbstractHandler.cs:line 181
   at Castle.MicroKernel.DefaultKernel.ResolveComponent(IHandler handler, Type service, Arguments additionalArguments, IReleasePolicy policy, Boolean ignoreParentContext) in C:\Users\Joseph\Source\Repos\Windsor\src\Castle.Windsor\MicroKernel\DefaultKernel.cs:line 751
   at Castle.MicroKernel.DefaultKernel.Castle.MicroKernel.IKernelInternal.Resolve(Type service, Arguments arguments, IReleasePolicy policy, Boolean ignoreParentContext) in C:\Users\Joseph\Source\Repos\Windsor\src\Castle.Windsor\MicroKernel\DefaultKernel_Resolve.cs:line 185
   at Castle.MicroKernel.DefaultKernel.Resolve(Type service, Arguments arguments) in C:\Users\Joseph\Source\Repos\Windsor\src\Castle.Windsor\MicroKernel\DefaultKernel_Resolve.cs:line 112
   at Castle.Windsor.WindsorContainer.Resolve[T]() in C:\Users\Joseph\Source\Repos\Windsor\src\Castle.Windsor\Windsor\WindsorContainer.cs:line 596
   at CastleTests.Activators.BestConstructorTestCase.Repro() in C:\Users\Joseph\Source\Repos\Windsor\src\Castle.Windsor.Tests\Activators\BestConstructorTestCase.cs:line 245
jnm2 commented 4 years ago

I'm guessing we have to get CanResolvePendingDependencies to start returning true before DefaultComponentActivator gets to do anything:

https://github.com/castleproject/Windsor/blob/294f7b4985503734a22b1d5786b4a32da4aa2296/src/Castle.Windsor/MicroKernel/Handlers/DefaultHandler.cs#L138-L160

jnm2 commented 4 years ago

The dependency model for ReproComponent has a missingDependencies field with both parameters from the second constructor in it.

I'm guessing the determination of which constructor will be used has to happen before the handler begins counting down which dependencies are missing. Do you see any inherent design issues with making that happen?