/// <summary>
        /// Constructs the invoke delegate using Expressions
        ///
        /// Serialize & Deserialize calls are only made when a serializer is provided.
        /// Context is only passed when customer method has context parameter.
        /// Lambda return type can be void.
        ///
        /// (inStream, context, outStream) =>
        /// {
        ///     var input = serializer.Deserialize(inStream);
        ///     var output = handler(input, context);
        ///     return serializer.Serialize(output);
        /// }
        ///
        /// </summary>
        /// <param name="customerObject">Wrapped customer object.</param>
        /// <param name="customerSerializerInstance">Instance of lambda input & output serializer.</param>
        /// <returns>Action delegate pointing to customer's handler.</returns>
        public Action <Stream, ILambdaContext, Stream> ConstructInvokeDelegate(object customerObject, object customerSerializerInstance, bool isPreJit)
        {
            var inStreamParameter  = Expression.Parameter(Types.StreamType, "inStream");
            var outStreamParameter = Expression.Parameter(Types.StreamType, "outStream");
            var contextParameter   = Expression.Parameter(typeof(ILambdaContext), "context");

            _logger.LogDebug($"UCL : Constructing input expression");
            var inputExpression = BuildInputExpressionOrNull(customerSerializerInstance, inStreamParameter, out var iLambdaContextType);

            if (isPreJit)
            {
                _logger.LogInformation("PreJit: inputExpression");
                UserCodeInit.InitDeserializationAssembly(inputExpression, inStreamParameter);
            }

            _logger.LogDebug($"UCL : Constructing context expression");
            var contextExpression = BuildContextExpressionOrNull(iLambdaContextType, contextParameter);

            _logger.LogDebug($"UCL : Constructing handler expression");
            var handlerExpression = CreateHandlerCallExpression(customerObject, inputExpression, contextExpression);

            _logger.LogDebug($"UCL : Constructing output expression");
            var outputExpression = CreateOutputExpression(customerSerializerInstance, outStreamParameter, handlerExpression);

            if (isPreJit)
            {
                _logger.LogInformation("PreJit: outputExpression");
                UserCodeInit.InitSerializationAssembly(outputExpression, outStreamParameter, CustomerOutputType);
            }

            _logger.LogDebug($"UCL : Constructing final expression");

            var finalExpression = Expression.Lambda <Action <Stream, ILambdaContext, Stream> >(outputExpression, inStreamParameter, contextParameter, outStreamParameter);

#if DEBUG
            var finalExpressionDebugView = typeof(Expression)
                                           .GetTypeInfo()
                                           .GetProperty("DebugView", BindingFlags.Instance | BindingFlags.NonPublic)
                                           .GetValue(finalExpression);
            _logger.LogDebug($"UCL : Constructed final expression:\n'{finalExpressionDebugView}'");
#endif

            _logger.LogDebug($"UCL : Compiling final expression");
            return(finalExpression.Compile());
        }
        /// <summary>
        /// Loads customer assembly, type, and method.
        /// After this call returns without errors, it is possible to invoke
        /// the customer method through the Invoke method.
        /// </summary>
        public void Init(Action <string> customerLoggingAction)
        {
            Assembly customerAssembly = null;

            try
            {
                _logger.LogDebug($"UCL : Parsing handler string '{_handlerString}'");
                _handler = new HandlerInfo(_handlerString);

                // Set the logging action private field on the Amazon.Lambda.Core.LambdaLogger type which is part of the
                // public Amazon.Lambda.Core package when it is loaded.
                AppDomain.CurrentDomain.AssemblyLoad += (sender, args) =>
                {
                    _logger.LogInformation($"UCL : Loaded assembly {args.LoadedAssembly.FullName} into default ALC.");
                    if (!_customerLoggerSetUpComplete && string.Equals(LambdaCoreAssemblyName, args.LoadedAssembly.GetName().Name, StringComparison.Ordinal))
                    {
                        _logger.LogDebug(
                            $"UCL : Load context loading '{LambdaCoreAssemblyName}', attempting to set {Types.LambdaLoggerTypeName}.{LambdaLoggingActionFieldName} to logging action.");
                        SetCustomerLoggerLogAction(args.LoadedAssembly, customerLoggingAction, _logger);
                        _customerLoggerSetUpComplete = true;
                    }
                };

                _logger.LogDebug($"UCL : Attempting to load assembly '{_handler.AssemblyName}'");
                customerAssembly = AssemblyLoadContext.Default.LoadFromAssemblyName(_handler.AssemblyName);
            }
            catch (FileNotFoundException fex)
            {
                _logger.LogError(fex, "An error occured on UCL Init");
                throw LambdaExceptions.ValidationException(Errors.UserCodeLoader.CouldNotFindHandlerAssembly, fex.FileName);
            }
            catch (LambdaValidationException validationException)
            {
                _logger.LogError(validationException, "An error occured on UCL Init");
                throw;
            }
            catch (Exception exception)
            {
                _logger.LogError(exception, "An error occured on UCL Init");
                throw LambdaExceptions.ValidationException(Errors.UserCodeLoader.UnableToLoadAssembly, _handler.AssemblyName);
            }

            _logger.LogDebug($"UCL : Attempting to load type '{_handler.TypeName}'");
            var customerType = customerAssembly.GetType(_handler.TypeName);

            if (customerType == null)
            {
                throw LambdaExceptions.ValidationException(Errors.UserCodeLoader.UnableToLoadType, _handler.TypeName, _handler.AssemblyName);
            }

            _logger.LogDebug($"UCL : Attempting to find method '{_handler.MethodName}' in type '{_handler.TypeName}'");
            CustomerMethodInfo = FindCustomerMethod(customerType);
            _logger.LogDebug($"UCL : Located method '{CustomerMethodInfo}'");

            _logger.LogDebug($"UCL : Validating method '{CustomerMethodInfo}'");
            UserCodeValidator.ValidateCustomerMethod(CustomerMethodInfo);

            var customerObject = GetCustomerObject(customerType);

            var customerSerializerInstance = GetSerializerObject(customerAssembly);

            _logger.LogDebug($"UCL : Constructing invoke delegate");

            var isPreJit = UserCodeInit.IsCallPreJit();
            var builder  = new InvokeDelegateBuilder(_logger, _handler, CustomerMethodInfo);

            _invokeDelegate = builder.ConstructInvokeDelegate(customerObject, customerSerializerInstance, isPreJit);
            if (isPreJit)
            {
                _logger.LogInformation("PreJit: PrepareDelegate");
                RuntimeHelpers.PrepareDelegate(_invokeDelegate);
            }
        }