Search code examples
c#constant-expressioncompiler-generated

Expression, Constant List, Compiler generated calss


I have this simple code:

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

}

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

While parsing the expression, that Index2 appears as a ConstantExpression. Then similar to many examples on this site and elsewhere, I have this method for parsing value of 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();
}

Problem is in return type of this method, type of return value is:

{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, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]"}

In QuickWatch it is possible to find the underlying List, but almost no way to refer it in code.


Solution

  • When you "close" a local variable, an hidden class is generated. What you see in the ConstantExpression is a reference to an instance of this hidden class.

    This:

    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));
    }
    

    is compiled to

    [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));
    }
    

    (see sharplab)

    The interesting parts are the private sealed class <>c__DisplayClass1_0 and the Expression.Constant(<>c__DisplayClass1_, typeof(<>c__DisplayClass1_0)).

    This hidden class is hidden. You can interact with it only through reflection.

    Your problem isn't really soluble in an easy way. For the specific example given:

    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();
    }
    

    For example just this:

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

    will break the code I gave.