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

表达式树报错:引用未定义变量,求动态JSON序列化代码修复方案

解决表达式树中局部变量未定义的异常问题

我最近在通过表达式树动态生成代码,实现模型对象的高效手动JSON序列化——目标是模型变更时不用手动更新序列化代码,但运行时却抛出了这个异常:

System.InvalidOperationException: 'variable 'sw' of type 'System.IO.StringWriter' referenced from scope '', but it is not defined'

是不是必须先声明变量再赋值?先看看我最初的实现代码:

public static Func<T, string> ConstructJsonParserFunction<T>() {
    List<Expression> methodBodyExpressions = new List<Expression>();
    ParameterExpression methodParameter = Expression.Parameter(typeof(T), "entity");
    ParameterExpression stringWriterExpression = Expression.Variable(typeof(StringWriter), "sw");
    ParameterExpression jsonTextWriterExpression = Expression.Variable(typeof(JsonTextWriter), "writer");
    ConstructorInfo jsonTextWriterConstructor = typeof(JsonTextWriter).GetConstructor(new Type[] { typeof(TextWriter) });
    MethodInfo jsonTextWriterMethod_WriteStartObject = typeof(JsonTextWriter).GetMethod("WriteStartObject");
    MethodInfo jsonTextWriterMethod_WritePropertyName = typeof(JsonTextWriter).GetMethods()
        .Where(mi => mi.Name == "WritePropertyName" && mi.GetParameters().Length == 1 && mi.GetParameters()[0].Name == "name")
        .First();
    Dictionary<Type, MethodInfo> jsonTextWriterMethods_WriteValue = new Dictionary<Type, MethodInfo>();
    foreach (MethodInfo method in typeof(JsonTextWriter).GetMethods().Where(mi => mi.Name == "WriteValue" && mi.GetParameters().Length == 1)) {
        jsonTextWriterMethods_WriteValue[method.GetParameters()[0].ParameterType] = method;
    }
    MethodInfo jsonTextWriterMethod_WriteEndObject = typeof(JsonTextWriter).GetMethod("WriteEndObject");
    MethodInfo stringWriterMethod_ToString = typeof(StringWriter).GetMethods()
        .Where(mi => mi.Name == "ToString" && mi.GetParameters().Length == 0)
        .First();
    methodBodyExpressions.Add(stringWriterExpression);
    methodBodyExpressions.Add(jsonTextWriterExpression);
    methodBodyExpressions.Add(Expression.Assign(stringWriterExpression, Expression.New(typeof(StringWriter))));
    methodBodyExpressions.Add(Expression.Assign(jsonTextWriterExpression, Expression.New(jsonTextWriterConstructor, stringWriterExpression)));
    methodBodyExpressions.Add(Expression.Call(jsonTextWriterExpression, jsonTextWriterMethod_WriteStartObject));
    foreach (PropertyInfo property in typeof(T).GetProperties().Where(p => Attribute.IsDefined(p, typeof(JsonPropertyAttribute)))) {
        methodBodyExpressions.Add(Expression.Call(jsonTextWriterExpression, jsonTextWriterMethod_WritePropertyName, Expression.Constant(property.Name)));
        methodBodyExpressions.Add(Expression.Call(jsonTextWriterExpression, jsonTextWriterMethods_WriteValue[property.PropertyType], Expression.Property(methodParameter, property)));
    }
    methodBodyExpressions.Add(Expression.Call(jsonTextWriterExpression, jsonTextWriterMethod_WriteEndObject));
    methodBodyExpressions.Add(Expression.Call(stringWriterExpression, stringWriterMethod_ToString));
    BlockExpression block = Expression.Block(methodBodyExpressions);
    return Expression.Lambda<Func<T, string>>(block, methodParameter).Compile();
}

当时的调试视图显示Block是这样的:

.Block() {
$sw;
$writer;
$sw = .New System.IO.StringWriter();
$writer = .New Newtonsoft.Json.JsonTextWriter($sw);
.Call $writer.WriteStartObject();
.Call $writer.WritePropertyName("CompanyId");
.Call $writer.WriteValue($entity.CompanyId);
.Call $writer.WritePropertyName("Name");
.Call $writer.WriteValue($entity.Name);
.Call $writer.WriteEndObject();
.Call $sw.ToString()
}

问题根源

仔细看就会发现,我虽然创建了sw和writer这两个变量,但BlockExpression根本不知道它们是当前作用域的局部变量——原代码只是把变量表达式直接丢进了方法体列表,没有在Block中显式声明这些变量,导致表达式树解析时认为变量未定义。

解决方案

要解决这个问题,必须满足两个关键条件:

  • 用Expression.Variable(type, "debugVarName")创建局部变量的ParameterExpression(这一步我已经做了);
  • 创建BlockExpression时,要把局部变量集合和主体表达式列表分开传递,让Block明确知道当前作用域内的变量。

同时还要删掉原代码中多余的两行——把变量本身加入方法体的操作完全没必要,因为Block会负责声明变量:

// 删掉这两行
methodBodyExpressions.Add(stringWriterExpression);
methodBodyExpressions.Add(jsonTextWriterExpression);

然后修改BlockExpression的创建代码,改成传递变量集合:

BlockExpression block = Expression.Block(
    new ParameterExpression[] { stringWriterExpression, jsonTextWriterExpression },
    methodBodyExpressions
);

修改后的完整代码如下:

public static Func<T, string> ConstructJsonParserFunction<T>() {
    List<Expression> methodBodyExpressions = new List<Expression>();
    ParameterExpression methodParameter = Expression.Parameter(typeof(T), "entity");
    ParameterExpression stringWriterExpression = Expression.Variable(typeof(StringWriter), "sw");
    ParameterExpression jsonTextWriterExpression = Expression.Variable(typeof(JsonTextWriter), "writer");
    ConstructorInfo jsonTextWriterConstructor = typeof(JsonTextWriter).GetConstructor(new Type[] { typeof(TextWriter) });
    MethodInfo jsonTextWriterMethod_WriteStartObject = typeof(JsonTextWriter).GetMethod("WriteStartObject");
    MethodInfo jsonTextWriterMethod_WritePropertyName = typeof(JsonTextWriter).GetMethods()
        .Where(mi => mi.Name == "WritePropertyName" && mi.GetParameters().Length == 1 && mi.GetParameters()[0].Name == "name")
        .First();
    Dictionary<Type, MethodInfo> jsonTextWriterMethods_WriteValue = new Dictionary<Type, MethodInfo>();
    foreach (MethodInfo method in typeof(JsonTextWriter).GetMethods().Where(mi => mi.Name == "WriteValue" && mi.GetParameters().Length == 1)) {
        jsonTextWriterMethods_WriteValue[method.GetParameters()[0].ParameterType] = method;
    }
    MethodInfo jsonTextWriterMethod_WriteEndObject = typeof(JsonTextWriter).GetMethod("WriteEndObject");
    MethodInfo stringWriterMethod_ToString = typeof(StringWriter).GetMethods()
        .Where(mi => mi.Name == "ToString" && mi.GetParameters().Length == 0)
        .First();
    
    // 移除了多余的变量添加操作
    methodBodyExpressions.Add(Expression.Assign(stringWriterExpression, Expression.New(typeof(StringWriter))));
    methodBodyExpressions.Add(Expression.Assign(jsonTextWriterExpression, Expression.New(jsonTextWriterConstructor, stringWriterExpression)));
    methodBodyExpressions.Add(Expression.Call(jsonTextWriterExpression, jsonTextWriterMethod_WriteStartObject));
    foreach (PropertyInfo property in typeof(T).GetProperties().Where(p => Attribute.IsDefined(p, typeof(JsonPropertyAttribute)))) {
        methodBodyExpressions.Add(Expression.Call(jsonTextWriterExpression, jsonTextWriterMethod_WritePropertyName, Expression.Constant(property.Name)));
        methodBodyExpressions.Add(Expression.Call(jsonTextWriterExpression, jsonTextWriterMethods_WriteValue[property.PropertyType], Expression.Property(methodParameter, property)));
    }
    methodBodyExpressions.Add(Expression.Call(jsonTextWriterExpression, jsonTextWriterMethod_WriteEndObject));
    methodBodyExpressions.Add(Expression.Call(stringWriterExpression, stringWriterMethod_ToString));
    
    // 显式传递局部变量给BlockExpression
    BlockExpression block = Expression.Block(
        new ParameterExpression[] { stringWriterExpression, jsonTextWriterExpression },
        methodBodyExpressions
    );
    return Expression.Lambda<Func<T, string>>(block, methodParameter).Compile();
}

修改后的调试视图会变成这样,变量已经被正确声明在Block作用域内:

.Block(System.IO.StringWriter $sw, Newtonsoft.Json.JsonTextWriter $writer) {
$sw = .New System.IO.StringWriter();
$writer = .New Newtonsoft.Json.JsonTextWriter($sw);
.Call $writer.WriteStartObject();
.Call $writer.WritePropertyName("CompanyId");
.Call $writer.WriteValue($entity.CompanyId);
.Call $writer.WritePropertyName("Name");
.Call $writer.WriteValue($entity.Name);
.Call $writer.WriteEndObject();
.Call $sw.ToString()
}

这样就能解决变量未定义的异常,让表达式树正确识别并使用局部变量了。

内容的提问来源于stack exchange,提问作者Brad Sherard

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 07:30:41