表达式树 属性 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 轮子。
我在设计时在代码中定义了一组映射,然后在提取一些数据后在 运行 时执行这些映射:
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 轮子。