表达式树 属性 Setter InvalidCastException

Expression Tree Property Setter InvalidCastException

我在设计时在代码中定义了一组映射,然后在提取一些数据后在 运行 时执行这些映射:

public class FormExtractionMap<T>
{
    public Expression<Func<T>> Destination { get; set; }

    public string Source { get; set; }

}

映射代码:

var extractionRequest = new ExtractionRequest<PlanningApplication>
    {
        Mapping = new List<FormExtractionMap<PlanningApplication>>
        {
            new FormExtractionMap<PlanningApplication> {Destination = x => x.Site.Address.MapCoordinate.Eastings, Source = "site_address_easting"},
            new FormExtractionMap<PlanningApplication> {Destination = x => x.Site.Address.MapCoordinate.Northings, Source = "site_address_northing"},
        }
    };

然后我查看每个映射表达式以获取源值(任何类型),然后将其分配给目标表达式(任何类型)。

        foreach (var extractionMap in extractionRequest.Mapping)
        {
            extractionRequest.ExtractTo.Set(extractionMap.Destination, form.GetValue(extractionMap.Source));
        }

然后我使用表达式扩展来创建 setter、编译并执行 属性 赋值。

    public static TEntity Set<TEntity, TProperty>(
        this TEntity obj,
        Expression<Func<TEntity, TProperty>> selector,
        TProperty value)
    {
        var setterExpr = CreateSetter(selector);
        setterExpr.Compile()(obj, value);
        return obj;
    }

    private static Expression<Action<TEntity, TProperty>> CreateSetter<TEntity, TProperty>(Expression<Func<TEntity, TProperty>> selector)
    {
        ParameterExpression valueParameterExpression = Expression.Parameter(typeof(TProperty), "value");
        Expression targetExpression = selector.Body is UnaryExpression ? ((UnaryExpression)selector.Body).Operand : selector.Body;
        var newValue = Expression.Parameter(selector.Body.Type);

        return Expression.Lambda<Action<TEntity, TProperty>>
        (
            Expression.Assign(targetExpression, Expression.Convert(valueParameterExpression, targetExpression.Type)),
            selector.Parameters.Single(),
            valueParameterExpression
        );
    }

如果 Source 是一个字符串并且 Destination 是一个字符串,则可以很好地分配值。如果目标是 setterExpr.Compile()(obj, value); 上的双精度数,我得到:

System.InvalidCastException : Unable to cast object of type 'System.String' to type 'System.Double'.

我以为“Expression.Convert”正在处理类型转换,但显然不是。请问我做错了什么?

所以,我最终解决了这个问题。 Expression.Convert 类似于显式转换 (Foo)Bar,而不是“Convert”所暗示的那样。我最终得到:

private static Expression<Action<TEntity, TProperty>> CreateSetter<TEntity, TProperty>(
            Expression<Func<TEntity, TProperty>> selector, Type valueParameterType)
        {
            ParameterExpression valueParameterExpression = Expression.Parameter(typeof(object), "value");
            Expression targetExpression = selector.Body is UnaryExpression ? ((UnaryExpression)selector.Body).Operand : selector.Body;
            
            var resultBody = ConvertToDestination(valueParameterExpression, valueParameterType, targetExpression);
            
            return Expression.Lambda<Action<TEntity, TProperty>>
            (
                Expression.Assign(targetExpression, resultBody),
                selector.Parameters.Single(),
                valueParameterExpression
            );

        }

        private static Expression ConvertToDestination(ParameterExpression valueParameterExpression, Type valueParameterType, Expression targetExpression)
        {
            if (valueParameterType == typeof(string))
            {
                switch (targetExpression.Type)
                {
                    case Type _ when targetExpression.Type == typeof(double):
                        return Expression.Call(typeof(Convert), "ToDouble", null, valueParameterExpression);

                    case Type _ when targetExpression.Type == typeof(int):
                        return Expression.Call(typeof(Convert), "ToInt", null, valueParameterExpression);

                    default:
                        return Expression.Convert(valueParameterExpression, targetExpression.Type);
                }
            }

            return Expression.Convert(valueParameterExpression, targetExpression.Type);
        }

但是,我认为它很混乱、冗长而且坦率地说没有必要,因为我能够在几个小时内使用 AutoMapper 实现类似的功能。 Automapper 在类型转换、缓存地图等方面做得更好。所以真正的解决方案是 re-factor 而不是 re-invent 轮子。