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

如何在表达式树中实现与Where一致的Contains变量引用?

如何让表达式树的Contains调用引用外部变量而非常量数组

当构建表达式树调用Enumerable.Contains时,当前代码直接将数组作为常量传入,生成的表达式为:

.Call System.Linq.Enumerable.Contains(
    .Constant<System.String[]>(System.String[]),
    $b.FirstName)

但手写LINQ语句customers.Where(x => abc.Contains(x.FirstName))生成的表达式会引用闭包中的外部变量:

.Call System.Linq.Enumerable.Contains(
    .Constant<Program+<>c__DisplayClass0_0>(Program+<>c__DisplayClass0_0).abc,
    $x.FirstName)

需要修改表达式树代码,使其生成的调用结构与手写LINQ一致。


解决方案

核心思路是模拟编译器处理闭包的逻辑:将外部变量封装到容器类(闭包类)中,在表达式树里引用该容器实例的成员,而非直接传入变量值作为常量。

方法一:利用匿名类型模拟闭包

匿名类型可以快速模拟编译器自动生成的闭包结构:

var parameter = Expression.Parameter(typeof(Customer), "b");
var property = Expression.Property(parameter, "FirstName");

// 用匿名类型封装外部变量,模拟闭包
var closure = new { abc = new string[] { "a", "b" } };
// 获取匿名类型的abc属性
var abcProperty = closure.GetType().GetProperty("abc");
// 构建引用闭包实例及其abc属性的表达式
var closureExpr = Expression.Constant(closure);
var abcExpr = Expression.Property(closureExpr, abcProperty);

// 获取Enumerable.Contains方法
MethodInfo contains = typeof(Enumerable)
    .GetMethods(BindingFlags.Static | BindingFlags.Public)
    .Where(x => x.Name == nameof(Enumerable.Contains))
    .Single(x => x.GetParameters().Length == 2)
    .MakeGenericMethod(property.Type);

// 构建Contains调用表达式
var body = Expression.Call(contains, abcExpr, property);

方法二:自定义闭包类

如果需要更明确的结构,可以手动定义闭包类:

// 自定义闭包类,持有外部变量
private class QueryClosure
{
    public string[] abc;
}

// 构建表达式树的代码
var parameter = Expression.Parameter(typeof(Customer), "b");
var property = Expression.Property(parameter, "FirstName");

var closure = new QueryClosure { abc = new string[] { "a", "b" } };
// 构建引用闭包实例abc字段的表达式
var abcExpr = Expression.Field(Expression.Constant(closure), nameof(QueryClosure.abc));

MethodInfo contains = typeof(Enumerable)
    .GetMethods(BindingFlags.Static | BindingFlags.Public)
    .Where(x => x.Name == nameof(Enumerable.Contains))
    .Single(x => x.GetParameters().Length == 2)
    .MakeGenericMethod(property.Type);

var body = Expression.Call(contains, abcExpr, property);

原理说明

手写LINQ时,C#编译器会自动生成一个隐藏的闭包类,将外部变量作为该类的字段/属性保存,然后在表达式中引用这个闭包实例的成员。我们的修改就是手动复现这个逻辑:

  • 避免使用Expression.Constant(abc)直接传入数组值,而是将数组放入容器对象
  • 通过Expression.Property或Expression.Field引用容器对象中的数组成员
  • 最终生成的表达式会和手写LINQ一致,引用闭包实例的变量而非嵌入常量

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 02:44:52