protected virtual async ValueTask <TAdapter> InvokeTargetGenericValueTaskAsync <TAdapter>(MethodInfo adapterMethod, AdapterInvocationInformation adapterInvocationInformation, object?[] targetArguments)
        {
            dynamic task;

            // ValueTasks are invariant and casting them is limited. Since await is forced to ValueTask<TDestination> by compiler,
            // dynamic cast must be used to box the invocation result
            try
            {
                task = (dynamic)adapterInvocationInformation.TargetMethod.Invoke(Target, targetArguments);
            }
            catch (TargetInvocationException ex)
            {
                ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
                // Should not be reached
                throw ex;
            }

            object?targetMethodReturnValue = await task.ConfigureAwait(false);

            // Get type of T from ValueTask<T>
            Type     adapterMethodReturnTypeWithoutTask = adapterMethod.ReturnType.GenericTypeArguments[0];
            Type     targetMethodReturnTypeWithoutTask  = adapterInvocationInformation.TargetMethod.ReturnType.GenericTypeArguments[0];
            TAdapter mappedReturnValue = (TAdapter)AdapterMapper.Map(targetMethodReturnValue, targetMethodReturnTypeWithoutTask, adapterMethodReturnTypeWithoutTask);

            if (_logger.IsEnabled(LogLevel.Trace))
            {
                _logger.Here(l => l.LogTrace("Target method result: {@0}, adapter method result: {@1}", targetMethodReturnValue, mappedReturnValue));
            }

            return(mappedReturnValue !);
        }
        /// <summary>
        /// Applies type and method mappings and invokes target method with mapped argument values. Does not call Proceed() because there is nothing to proceed to - this interceptor should always be the last one.
        /// </summary>
        /// <param name="invocation">Encapsulates an invocation of a proxied method.</param>
        public virtual void Intercept(IInvocation invocation)
        {
            if (_logger.IsEnteringExitingEnabled())
            {
                _logger.Here(l => l.Entering(invocation.ToLoggerString(simpleType: true) !, invocation.Arguments, invocation.ReturnValue));
            }

            MethodInfo adapterMethod = invocation.Method;
            AdapterInvocationInformation adapterInvocationInformation = AdapterToTargetMethodDictionary.GetOrAdd(adapterMethod, item =>
            {
                Type[] adapterMethodTypes           = adapterMethod.GetParameters().Select(item => item.ParameterType).ToArray();
                Type[] targetMethodTypes            = MapSupportedTypes(adapterMethodTypes);
                MethodInfo targetMethod             = MapTargetMethod(TargetType, adapterMethod, targetMethodTypes);
                AdapterInvocationInformation result = PrepareAdapterInvocationInformation(adapterMethod, targetMethod, adapterMethodTypes, targetMethodTypes);
                return(result);
            });

            object?[] adapterArguments = invocation.Arguments;
            object?[] targetArguments  = new object?[adapterArguments.Length];

            for (int i = 0; i < targetArguments.Length; i++)
            {
                targetArguments[i] = AdapterMapper.Map(adapterArguments[i], adapterInvocationInformation.AdapterMethodParameterTypes[i], adapterInvocationInformation.TargetMethodParameterTypes[i]);
            }

            object?mappedReturnValue;

            switch (adapterInvocationInformation.TargetInvocationType)
            {
            case InvocationTypes.Sync:
                mappedReturnValue = InvokeTargetSync(adapterMethod, adapterInvocationInformation, targetArguments, invocation);
                break;

            case InvocationTypes.GenericTask:
                mappedReturnValue = adapterInvocationInformation.InvocationHelper !.Invoke(this, new object[] { adapterMethod, adapterInvocationInformation, targetArguments });
                break;

            case InvocationTypes.Task:
                mappedReturnValue = InvokeTargetTaskAsync(adapterMethod, adapterInvocationInformation, targetArguments);
                break;

            case InvocationTypes.GenericValueTask:
                mappedReturnValue = adapterInvocationInformation.InvocationHelper !.Invoke(this, new object[] { adapterMethod, adapterInvocationInformation, targetArguments });
                break;

            case InvocationTypes.ValueTask:
                mappedReturnValue = InvokeTargetValueTaskAsync(adapterMethod, adapterInvocationInformation, targetArguments);
                break;

            default: throw new NotImplementedException();
            }
            invocation.ReturnValue = mappedReturnValue;

            // Return values are logged elsewhere
            _logger.Here(l => l.Exiting());
        }
        protected virtual object?InvokeTargetSync(MethodInfo adapterMethod, AdapterInvocationInformation adapterInvocationInformation, object?[] targetArguments, IInvocation invocation)
        {
            object?returnValue;

            try
            {
                returnValue = adapterInvocationInformation.TargetMethod.Invoke(Target, targetArguments);
            }
            catch (TargetInvocationException ex)
            {
                ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
                // Should not be reached
                throw ex;
            }

            object?mappedReturnValue = AdapterMapper.Map(returnValue, adapterInvocationInformation.TargetMethod.ReturnType, adapterMethod.ReturnType);

            if (_logger.IsEnabled(LogLevel.Trace))
            {
                _logger.Here(l => l.LogTrace("Target method result: {@0}, adapter method result: {@1}", returnValue, mappedReturnValue));
            }

            for (int i = 0; i < adapterInvocationInformation.AdapterMethodParameterTypes.Length; i++)
            {
                if (adapterInvocationInformation.AdapterMethodParameterTypes[i].IsByRef)
                {
                    invocation.Arguments[i] = AdapterMapper.Map(targetArguments[i], adapterInvocationInformation.TargetMethodParameterTypes[i], adapterInvocationInformation.AdapterMethodParameterTypes[i]);
                    if (_logger.IsEnabled(LogLevel.Trace))
                    {
                        _logger.Here(l => l.LogTrace($"Updated adapter method argument. Target method argument[{i}]: {{@0}}, adapter method argument[{i}]: {{@1}}", targetArguments[i], invocation.Arguments[i]));
                    }
                }
            }

            return(mappedReturnValue);
        }