0

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.

1 Answers1

1

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.

xanatos
  • 109,618
  • 12
  • 197
  • 280
  • Thanks, this gave me an overview, and sorry for primitive questions, could you please collaborate more, where can I find a sample of using reflection like this. And one more thing is that my parser class doesn't know from the beginning what Type of data (wrapped in hidden class) to be waiting for. – Alireza Ahmadi Rad Dec 26 '20 at 14:08
  • @AlirezaAhmadiRad It is unclear exactly what you want... In general the examples given in the other response are made for just the example given (extract the `30`). With more complex objects, or calls made in a different way they won't work. If you want `Index2` *in this case* (and I repeat, *in this case), you just have to extract the `Expression.Field` that is using the `Expression.Constant` (so just one step "up") – xanatos Dec 26 '20 at 14:27
  • To comprehend why the problem is complex, let's make your query more complex: `MyWhere(a => !Indexes2.Contains(a.a1));`... Now we simply added a `!`... But we could be more cruel... `MyWhere(a => !Indexes2.Contains(a.a1) && Indexes2.Count > 1);` and so on... The full problem is comples enough that [the premiere library about building queries from Expression trees still hasn't solved it](https://github.com/re-motion/Relinq/pull/14). – xanatos Dec 26 '20 at 14:36
  • Give me some time and I'm understanding you point about using Expression.Field. The whole problem doesn't seem that much complex. I'm creating a wrapper class that for each Left/Right expresion creates a new instance of itself recursively. that matter of putting a ! is already handled. – Alireza Ahmadi Rad Dec 26 '20 at 15:00
  • I'm trying to use ((FieldInfo)member.Member).GetValue(), but again can't find relevant object which is to be used as argument of GetValue, Sorry, got completely lost! – Alireza Ahmadi Rad Dec 26 '20 at 15:14
  • @AlirezaAhmadiRad `private static object ConstantValue(ConstantExpression member)` must become a `private static object FieldExpression(MemberExpression member)` and you must pass to it the `MemberExpression` that is using the `ConstantExpression`... – xanatos Dec 26 '20 at 15:17
  • @AlirezaAhmadiRad Wrote example code *that works just for the example you gave* – xanatos Dec 26 '20 at 15:35