Пример #1
0
        /// <summary>
        /// Modifies RegistrationEntries which have [ExcelMapArrayFunction],
        /// converting IEnumerable parameters to and from Excel Ranges (i.e. object[,]).
        /// This allows idiomatic .NET functions (which use sequences and lists) to be used as UDFs.
        ///
        /// Supports the use of Excel Array formulae where a UDF returns an enumerable.
        ///
        /// 1-dimensional Excel arrays are mapped automatically to/from IEnumerable.
        /// 2-dimensional Excel arrays can be mapped to a single function parameter with
        /// [ExcelMapPropertiesToColumnHeaders].
        /// </summary>
        public static IEnumerable <ExcelFunctionRegistration> ProcessMapArrayFunctions(
            this IEnumerable <ExcelFunctionRegistration> registrations,
            ParameterConversionConfiguration config = null)
        {
            foreach (var reg in registrations)
            {
                if (!(reg.FunctionAttribute is ExcelMapArrayFunctionAttribute))
                {
                    // Not considered at all
                    yield return(reg);

                    continue;
                }

                try
                {
                    var inputShimParameters = reg.FunctionLambda.Parameters.ZipSameLengths(reg.ParameterRegistrations,
                                                                                           (p, r) => new ShimParameter(p.Type, r, config)).ToList();
                    var resultShimParameter = new ShimParameter(reg.FunctionLambda.ReturnType, reg.ReturnRegistration, config);

                    // create the shim function as a lambda, using reflection
                    LambdaExpression shim = MakeObjectArrayShim(
                        reg.FunctionLambda,
                        inputShimParameters,
                        resultShimParameter);

                    // create a description of the function, with a list of the output fields
                    string functionDescription = "Returns " + resultShimParameter.HelpString;

                    // create a description of each parameter, with a list of the input fields
                    var parameterDescriptions = inputShimParameters.Select(shimParameter => "Input " +
                                                                           shimParameter.HelpString).ToArray();

                    // all ok so far - modify the registration
                    reg.FunctionLambda = shim;
                    if (String.IsNullOrEmpty(reg.FunctionAttribute.Description))
                    {
                        reg.FunctionAttribute.Description = functionDescription;
                    }
                    for (int param = 0; param != reg.ParameterRegistrations.Count; ++param)
                    {
                        if (String.IsNullOrEmpty(reg.ParameterRegistrations[param].ArgumentAttribute.Description))
                        {
                            reg.ParameterRegistrations[param].ArgumentAttribute.Description =
                                parameterDescriptions[param];
                        }
                    }
                }
                catch
                {
                    // failed to shim, just pass on the original
                }
                yield return(reg);
            }
        }
Пример #2
0
        /// <summary>
        /// Function which creates a shim for a target method.
        /// The target method is expected to take 1 or more enumerables of various types, and return a single enumerable of another type.
        /// The shim is a lambda expression which takes 1 or more object[,] parameters, and returns a single object[,]
        /// The first row of each array defines the field names, which are mapped to the public properties of the
        /// input and return types.
        /// </summary>
        /// <returns></returns>
        private static LambdaExpression MakeObjectArrayShim(
            LambdaExpression targetMethod,
            IList <ShimParameter> inputShimParameters,
            ShimParameter resultShimParameter)
        {
            var nParams = targetMethod.Parameters.Count;

            var compiledTargetMethod = targetMethod.Compile();

            // create a delegate, object*n -> object
            // (simpler, but probably slower, alternative to building it all out of Expressions)
            ParamsDelegate shimDelegate = inputObjectArray =>
            {
                try
                {
                    if (inputObjectArray.GetLength(0) != nParams)
                    {
                        throw new InvalidOperationException(
                                  $"Expected {nParams} params, received {inputObjectArray.GetLength(0)}");
                    }

                    var targetMethodInputs = new object[nParams];

                    for (int i = 0; i != nParams; ++i)
                    {
                        try
                        {
                            targetMethodInputs[i] = inputShimParameters[i].ConvertShimToTarget(inputObjectArray[i]);
                        }
                        catch (Exception e)
                        {
                            throw new InvalidOperationException($"Failed to convert parameter {i + 1}: {e.Message}");
                        }
                    }

                    var targetMethodResult = compiledTargetMethod.DynamicInvoke(targetMethodInputs);

                    return(resultShimParameter.ConvertTargetToShim(targetMethodResult));
                }
                catch (Exception e)
                {
                    return(new object[, ] {
                        { ExcelError.ExcelErrorValue }, { e.Message }
                    });
                }
            };

            // convert the delegate back to a LambdaExpression
            var args        = targetMethod.Parameters.Select(param => Expression.Parameter(typeof(object))).ToList();
            var paramsParam = Expression.NewArrayInit(typeof(object), args);
            var closure     = Expression.Constant(shimDelegate.Target);
            var call        = Expression.Call(closure, shimDelegate.Method, paramsParam);

            return(Expression.Lambda(call, args));
        }