You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 20:02:39