/// <summary> /// Gets the <see cref="ConstructorBinding"/> for the <see cref="DeclaredType"/> using the /// targets available in the <paramref name="context"/> for dependency lookup. /// /// The constructor is either resolved by checking available targets for the best match, or is pre-selected /// on construction (<see cref="Ctor"/> will be non-null in this case). /// </summary> /// <param name="context">The current compilation context.</param> /// <exception cref="AmbiguousMatchException">If more than one constructor can be bound with an equal amount of all-resolved /// arguments or default arguments.</exception> /// <exception cref="InvalidOperationException">If no sutiable constructors can be found.</exception> /// <remarks>All implementations of <see cref="ITargetCompiler"/> should first use this method to find /// the constructor to be called, and the arguments that are to be supplied to it. /// /// This method also builds a list of <see cref="MemberBinding"/>s for properties or fields on the type /// which are to be set with values from the container after construction. The exact behaviour of this is /// controlled by the behaviour set on the <see cref="MemberBindingBehaviour"/> property, or, if <c>null</c> /// then the method attempts to resolve an <see cref="IMemberBindingBehaviour"/> from the /// <see cref="ResolveContext.Container"/> of the <see cref="ResolveContext"/> set on the /// <see cref="ICompileContext.ResolveContext"/> of the passed <paramref name="context"/>.</remarks> public ConstructorBinding Bind(ICompileContext context) { ConstructorInfo ctor = this._ctor; ParameterBinding[] boundArgs = ParameterBinding.None; if (ctor == null) { // have to go searching for the best constructor match for the current context, // which will also give us our arguments var publicCtorGroups = GetPublicConstructorGroups(DeclaredType); var ctorsWithBindingsGrouped = publicCtorGroups.Select(g => g.Select(ci => new { ctor = ci, // filtered collection of parameter bindings along with the actual ITarget that is resolved for each // NOTE: we're using the default behaviour of ParameterBinding here which is to auto-resolve an argument // value or to use the parameter's default if it is optional. bindings = ParameterBinding.BindMethod(ci, this._namedArgs) // ci.GetParameters().Select(pi => new ParameterBinding(pi)) .Select(pb => new { Parameter = pb, RezolvedArg = pb.Resolve(context) }) .Where(bp => bp.RezolvedArg != null).ToArray() // (ABOVE) only include bindings where a target was found - means we can quickly // determine if all parameters are bound by checking the array length is equal to the // number of parameters on the constructor itself (BELOW) }).Where(a => a.bindings.Length == g.Key).ToArray() ).Where(a => a.Length > 0).ToArray(); // filter to where there is at least one successfully bound constructor if (ctorsWithBindingsGrouped.Length == 0) { // No constructors for which we could bind all parameters with either a mix of resolved or default arguments. // so we'll auto-bind to the constructor with the most parameters - if there is one - leaving the application // with the responsibility of ensuring that the correct registrations are made in the target container, or // in the container supplied at resolve-time, to satisfy the constructor's dependencies. if (publicCtorGroups.Length != 0) { var mostGreedy = publicCtorGroups[0].ToArray(); if (mostGreedy.Length > 1) { // see if we can get a single constructor which has the fewest number of optionals // - even though we haven't got configured services for all (or even any), we can choose // one based on the greedy rule. var leastOptional = mostGreedy.Select(c => new { ctor = c, optionalCount = c.GetParameters().Count(p => p.IsOptional) }) .OrderBy(c => c.optionalCount) .GroupBy(c => c.optionalCount) .First() .ToArray(); if (leastOptional.Length == 1) { ctor = leastOptional[0].ctor; boundArgs = ParameterBinding.BindWithRezolvedArguments(ctor).ToArray(); } else { throw new AmbiguousMatchException(string.Format(ExceptionResources.MoreThanOneConstructorFormat, DeclaredType, string.Join(", ", mostGreedy.AsEnumerable()))); } } else { ctor = mostGreedy[0]; boundArgs = ParameterBinding.BindWithRezolvedArguments(ctor).ToArray(); } } else { throw new InvalidOperationException(string.Format(ExceptionResources.NoApplicableConstructorForContextFormat, DeclaredType)); } } else { // managed to bind at least constructor up front to registered targets or defaults // get the greediest constructors with successfully bound parameters. var mostBound = ctorsWithBindingsGrouped[0]; // get the first result var toBind = mostBound[0]; // if there is only one, then we can move on to code generation if (mostBound.Length > 1) { // the question now is one of disambiguation: // choose the one with the fewest number of targets with ITarget.UseFallback set to true // if we still can't disambiguate, then we have an exception. var fewestFallback = mostBound.GroupBy(a => a.bindings.Count(b => b.RezolvedArg.UseFallback)).FirstOrDefault().ToArray(); if (fewestFallback.Length > 1) { throw new AmbiguousMatchException(string.Format(ExceptionResources.MoreThanOneBestConstructorFormat, DeclaredType, string.Join(", ", fewestFallback.Select(a => a.ctor)))); } toBind = fewestFallback[0]; } ctor = toBind.ctor; boundArgs = toBind.bindings.Select(a => a.Parameter).ToArray(); } } // we allow for no parameter bindings to be provided on construction, and have them dynamically determined // also allow for some to be ommitted (check by testing reference equality on the parameterinfo) else if (this._parameterBindings.Length != ctor.GetParameters().Length || !ctor.GetParameters().All(p => this._parameterBindings.FirstOrDefault(pb => pb.Parameter == p) != null)) { // just need to generate the bound parameters - nice and easy // because the constructor was provided up-front, we don't check whether the target can be resolved boundArgs = ParameterBinding.BindMethod(ctor, this._parameterBindings); } else { boundArgs = this._parameterBindings; } // use either the member binding behaviour that was passed on construction, or locate the // option from the compile context's target container. var memberBindingBehaviour = MemberBindingBehaviour ?? context.GetOption(ctor.DeclaringType, Rezolver.MemberBindingBehaviour.BindNone); return(new ConstructorBinding(ctor, boundArgs, memberBindingBehaviour?.GetMemberBindings(context, DeclaredType))); }