IEnumerable <MethodDataAdapter> GetMethodCandidates(IInvocation invocation)
        {
            var abstractionType    = ((DispatchedObject)invocation.Proxy).AbstractionType;
            var implementationType = Node.Mappings[abstractionType];
            var methodInfoAdapter  = _methodInfoAdapters[invocation.Method];

            var bindingFlags = methodInfoAdapter.IsStatic
                ? BindingFlags.Public | BindingFlags.Static
                : BindingFlags.Public | BindingFlags.Instance;

            var candidateMethods = methodInfoAdapter.IsConstructor
                ? implementationType.GetConstructors(bindingFlags)
                : (IEnumerable <MethodBase>)implementationType
                                   .GetMethods(bindingFlags)
                                   .Where(m => methodInfoAdapter.AliasNames == null && invocation.Method.Name.Equals(m.Name) ||
                                          methodInfoAdapter.AliasNames != null && methodInfoAdapter.AliasNames.Contains(m.Name));

            var abstractionParameters = invocation.Method.GetParameters();

            foreach (var candidate in candidateMethods)
            {
                var implementationParameters = candidate.GetParameters();
                if (abstractionParameters.Length != implementationParameters.Length)
                {
                    continue;
                }

                var parameterAdapters  = new List <ITypeAdapter>();
                var parameterAdaptable = true;
                for (var i = 0; i < abstractionParameters.Length; i++)
                {
                    var parameterAdapter = TypeAdapter.Build(Node.Mappings, abstractionParameters[i].ParameterType, implementationParameters[i].ParameterType);
                    if (parameterAdapter == null)
                    {
                        parameterAdaptable = false; break;
                    }
                    parameterAdapters.Add(parameterAdapter);
                }

                if (!parameterAdaptable)
                {
                    continue;
                }

                var implementationReturnType = candidate is ConstructorInfo c ? c.DeclaringType : ((MethodInfo)candidate).ReturnType;
                var returnAdapter            = TypeAdapter.Build(Node.Mappings, implementationReturnType, invocation.Method.ReturnType);
                if (returnAdapter != null)
                {
                    yield return(new MethodDataAdapter(candidate, parameterAdapters, returnAdapter));
                }
            }
        }
        void MakeHandler(IInvocation invocation)
        {
            var abstractionType    = ((DispatchedObject)invocation.Proxy).AbstractionType;
            var implementationType = Node.Mappings[abstractionType];

            var candidates = this.GetMethodCandidates(invocation).ToArray();

            if (candidates.Length == 0)
            {
                _knownHandlers.Add(invocation.Method, i => throw new MethodNotImplementedException(i.Method, implementationType));
                return;
            }

            if (candidates.Length > 1)
            {
                _knownHandlers.Add(invocation.Method, i => throw new AmbiguousMethodException(candidates.Select(c => c.Method)));
                return;
            }

            var candidate       = candidates.Single();
            var variables       = new List <ParameterExpression>();
            var body            = new List <Expression>();
            var byRefAssignBack = new List <Expression>();

            var invocationDefinition = Expression.Parameter(typeof(IInvocation));
            var invocationArguments  = Expression.MakeMemberAccess(invocationDefinition, Ref.IInvocation_Arguments);

            for (var i = 0; i < candidate.ParameterAdapters.Length; i++)
            {
                var adapter        = candidate.ParameterAdapters[i];
                var byRef          = adapter.To.IsByRef;
                var variableType   = byRef ? adapter.To.GetElementType() : adapter.To;
                var variable       = Expression.Variable(variableType);
                var assignVariable =
                    Expression.Assign(
                        variable,
                        Expression.Convert(
                            Expression.Call(
                                Expression.Constant(adapter),
                                Ref.ITypeAdapter_Adapt,
                                Expression.ArrayAccess(invocationArguments, Expression.Constant(i)),
                                Expression.Constant(this)),
                            variableType));

                variables.Add(variable);
                body.Add(assignVariable);

                if (byRef)
                {
                    var assignBackAdapter = TypeAdapter.Build(Node.Mappings, variableType, adapter.From.GetElementType());
                    byRefAssignBack.Add(
                        Expression.Assign(
                            Expression.ArrayAccess(invocationArguments, Expression.Constant(i)),
                            Expression.Convert(
                                Expression.Call(
                                    Expression.Constant(assignBackAdapter),
                                    Ref.ITypeAdapter_Adapt,
                                    variable,
                                    Expression.Constant(this)),
                                typeof(object))));
                }
            }

            Expression invocationReturnValue = Expression.MakeMemberAccess(invocationDefinition, Ref.IInvocation_ReturnValue);

            if (candidate.Method is ConstructorInfo ctor)
            {
                body.Add(
                    Expression.Assign(
                        invocationReturnValue,
                        Expression.Convert(
                            Expression.Call(
                                Expression.Constant(candidate.ReturnAdapter),
                                Ref.ITypeAdapter_Adapt,
                                Expression.New(ctor, variables),
                                Expression.Constant(this)),
                            typeof(object))));
            }
            else if (candidate.Method is MethodInfo method)
            {
                Expression invokeMethod = method.IsStatic
                    ? Expression.Call(method, variables)
                    : Expression.Call(
                    Expression.Convert(
                        Expression.MakeMemberAccess(
                            Expression.Convert(
                                Expression.MakeMemberAccess(invocationDefinition, Ref.IInvocation_Proxy),
                                typeof(DispatchedObject)),
                            Ref.DispatchedObject_Implementation),
                        implementationType),
                    method,
                    variables);

                if (method.ReturnType == typeof(void))
                {
                    body.Add(invokeMethod);
                }
                else
                {
                    body.Add(
                        Expression.Assign(
                            invocationReturnValue,
                            Expression.Convert(
                                Expression.Call(
                                    Expression.Constant(candidate.ReturnAdapter),
                                    Ref.ITypeAdapter_Adapt,
                                    Expression.Convert(invokeMethod, typeof(object)),
                                    Expression.Constant(this)),
                                typeof(object))));
                }
            }

            if (byRefAssignBack.Any())
            {
                body.AddRange(byRefAssignBack);
            }

            var handler = Expression.Lambda <Action <IInvocation> >(WrapExceptions(Expression.Block(variables, body)), invocationDefinition);

            _knownHandlers.Add(invocation.Method, handler.Compile());
        }