Expression Tree属性赋值时InvalidCastException问题排查
问题分析
你遇到的InvalidCastException根源其实不是Expression.Convert没生效,而是调用Set方法时的参数类型不匹配。
仔细看你的调用代码:
extractionRequest.ExtractTo.Set(extractionMap.Destination, form.GetValue(extractionMap.Source));
form.GetValue返回的是object类型(实际是字符串),而Set方法的第二个参数是TProperty(这里是double)。当你把字符串直接传给double类型的参数时,CLR会尝试执行强制类型转换(比如(double)"123"),这在C#里是不允许的——字符串转数值需要显式解析(比如double.Parse),不是单纯的强制转换。
而你写的Expression.Convert其实是多余的:它只负责CLR内置的显式/隐式转换(比如int转double),根本没机会处理字符串转double的逻辑,因为在进入表达式树之前,参数转换就已经失败了。
解决方案
我们需要在赋值前先完成类型解析转换,有两种优雅的实现方式:
方式一:修改Set方法,支持object类型参数并自动转换
把Set的第二个参数改为object,内部用Convert.ChangeType处理字符串到数值的解析,这样就不用手动转换每个值了:
public static TEntity Set<TEntity, TProperty>( this TEntity obj, Expression<Func<TEntity, TProperty>> selector, object value) // 改为object类型 { // 先把object值转换为目标类型TProperty TProperty convertedValue = ConvertToTargetType<TProperty>(value); var setterExpr = CreateSetter(selector); setterExpr.Compile()(obj, convertedValue); return obj; } // 辅助方法:处理通用类型转换(包括字符串转数值) private static TProperty ConvertToTargetType<TProperty>(object value) { if (value == null) return default; // 如果已经是目标类型,直接返回 if (value is TProperty typedValue) return typedValue; // 处理可空类型(比如double?) var targetType = Nullable.GetUnderlyingType(typeof(TProperty)) ?? typeof(TProperty); try { // Convert.ChangeType会自动处理字符串到数值/日期等常见转换 return (TProperty)Convert.ChangeType(value, targetType); } catch (Exception ex) { throw new InvalidOperationException( $"Failed to convert value '{value}' to type {targetType.Name}", ex); } } // 简化CreateSetter方法(去掉没用的newValue参数) private static Expression<Action<TEntity, TProperty>> CreateSetter<TEntity, TProperty>( Expression<Func<TEntity, TProperty>> selector) { var valueParam = Expression.Parameter(typeof(TProperty), "value"); Expression targetExpr = selector.Body is UnaryExpression ? ((UnaryExpression)selector.Body).Operand : selector.Body; // 现在不需要Expression.Convert了,因为valueParam和targetExpr类型一致 var assignExpr = Expression.Assign(targetExpr, valueParam); return Expression.Lambda<Action<TEntity, TProperty>>( assignExpr, selector.Parameters.Single(), valueParam); }
方式二:调用Set前手动转换(适合简单场景)
如果你不想修改Set方法,也可以在调用时先把字符串解析为目标类型:
foreach (var extractionMap in extractionRequest.Mapping) { var sourceValue = form.GetValue(extractionMap.Source); // 根据目标类型做解析 var targetType = extractionMap.Destination.Body.Type; var convertedValue = Convert.ChangeType(sourceValue, targetType); // 用动态类型适配泛型参数 extractionRequest.ExtractTo.Set(extractionMap.Destination, (dynamic)convertedValue); }
不过这种方式需要依赖动态类型,不如方式一的通用转换逻辑优雅,推荐优先用方式一。
为什么原来的
Expression.Convert没用? Expression.Convert的作用是生成CLR内置的转换表达式,比如:
- 值类型之间的转换(
int→double) - 引用类型的向上/向下转型(
object→string)
但它不会生成解析逻辑(比如string→double需要调用double.Parse),这类转换不属于CLR内置的强制转换范畴,所以Expression.Convert处理不了。
内容的提问来源于stack exchange,提问作者sarin
相关产品推荐
相关产品推荐

