Пример #1
0
        /// <summary>
        /// Builds an expression from the specified target for the given <see cref="IExpressionCompileContext" />
        /// </summary>
        /// <param name="target">The target whose expression is to be built.</param>
        /// <param name="context">The compilation context.</param>
        /// <param name="compiler">The expression compiler to be used to build any other expressions for targets
        /// which might be required by the <paramref name="target" />.  Note that unlike on the interface, where this
        /// parameter is optional, this will always be provided</param>
        /// <exception cref="NotImplementedException"></exception>
        protected override Expression Build(DelegateTarget target, IExpressionCompileContext context, IExpressionCompiler compiler)
        {
            var bindings = ParameterBinding.BindWithRezolvedArguments(target.Factory.GetMethodInfo());

            return(Expression.Invoke(Expression.Constant(target.Factory),
                                     bindings.Select(b => b.Parameter.ParameterType == typeof(ResolveContext) ?
                                                     context.ResolveContextParameterExpression
                    : compiler.Build(b.Target, context.NewContext(b.Parameter.ParameterType)))));
        }
        /// <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)));
        }