表达式、常量列表、编译器生成的类

问题描述

我有这个简单的代码

public void MyWhere( Expression<Func<T,bool>> predicate)
{

}

List<string> Indexes2 = new List<string>();
Indexes2.Add("abc");
MyWhere(a=>Index2.Contains(a.a1));

在解析表达式时,Index2 显示为 ConstantExpression。然后类似于 this site 和其他地方的许多示例,我有这种解析 ConatantExpression 值的方法

private static object ConstantValue(ConstantExpression member)
{
    // source: http://stackoverflow.com/a/2616980/291955
    var objectMember = Expression.Convert(member,typeof(object));
    var getterLambda = Expression.Lambda<Func<object>>(objectMember);
    var getter = getterLambda.Compile();
    return getter();
}

问题在于该方法的返回类型,返回值类型为:

{Name = "c__displayClass38_0" FullName = "S_Common.A_Dictionary`2+c__displayClass38_0[[S_Common.StringIndex,S_Common,Version=1.0.0.0,Culture=neutral,PublicKeyToken=null],[DummyTestApp.test,DummyTestApp,PublicKeyToken=null]]"}

在 QuickWatch 中可以找到底层 List,但几乎无法在代码中引用它。

解决方法

当你“关闭”一个局部变量时,会生成一个隐藏类。您在 ConstantExpression 中看到的是对这个隐藏类的实例的引用。

这个:

public void MyWhere<T>(Expression<Func<T,bool>> predicate)
{
}

public void M() 
{
    List<string> Indexes2 = new List<string>();
    Indexes2.Add("abc");
    MyWhere<String>(a => Indexes2.Contains(a));
}

被编译为

[CompilerGenerated]
private sealed class <>c__DisplayClass1_0
{
    public List<string> Indexes2;
}

public void MyWhere<T>(Expression<Func<T,bool>> predicate)
{
}

public void M()
{
    <>c__DisplayClass1_0 <>c__DisplayClass1_ = new <>c__DisplayClass1_0();
    <>c__DisplayClass1_.Indexes2 = new List<string>();
    <>c__DisplayClass1_.Indexes2.Add("abc");
    ParameterExpression parameterExpression = Expression.Parameter(typeof(string),"a");
    MemberExpression instance = Expression.Field(Expression.Constant(<>c__DisplayClass1_,typeof(<>c__DisplayClass1_0)),FieldInfo.GetFieldFromHandle((RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/));
    MethodInfo method = (MethodInfo)MethodBase.GetMethodFromHandle((RuntimeMethodHandle)/*OpCode not supported: LdMemberToken*/,typeof(List<string>).TypeHandle);
    Expression[] array = new Expression[1];
    array[0] = parameterExpression;
    MethodCallExpression body = Expression.Call(instance,method,array);
    ParameterExpression[] array2 = new ParameterExpression[1];
    array2[0] = parameterExpression;
    MyWhere(Expression.Lambda<Func<string,bool>>(body,array2));
}

(见sharplab

有趣的部分是 private sealed class <>c__DisplayClass1_0Expression.Constant(<>c__DisplayClass1_,typeof(<>c__DisplayClass1_0))

这个隐藏的类是隐藏的。你只能通过反射与它互动。

您的问题并不能以简单的方式真正解决。对于给出的特定示例:

public static void MyWhere<T>(Expression<Func<T,bool>> predicate)
{
    var body = predicate.Body;

    // .Contains(...)
    var contains = body as MethodCallExpression;

    // Indexes2
    var field = contains.Object;

    // Need boxing only for value types
    var boxIfNecessary = field.Type.IsValueType ? (Expression)Expression.Convert(field,typeof(object)) : field;
    var lambda = Expression.Lambda<Func<object>>(boxIfNecessary);
    var compiled = lambda.Compile();

    // Indexes of type List<string>()
    var value = compiled();
}

例如:

MyWhere<string>(a => Enumerable.Contains(Indexes2,a));

会破坏我给出的代码。