Expression Tree with string assignment and getting

2019-06-26 11:07发布

问题:

I have built my own SQL Query builder that breaks apart an Expression, however, I'm having an issue trying to get the value of string defined in the same function as the lambda expression.

Here is what I am trying to do in console app:

private static void MyBuilderTest()
{
    var sqlBuilder = new SqlBuilder();

    // Doesn't work -- NEED GUIDANCE HERE
    var testValue = "Test";  // Defined in the same function as the lambda below

    sqlBuilder.Select<FooObject>(o => o.FooValue == testValue);

    // Works
    var someObject = new SomeObject { SomeValue = "classTest };

    sqlBuilder.Select<FooObject>(o => o.FooValue == someObject.SomeValue);
}

In my builder it subclasses from ExpressionVisitor, and I override the VisitMember. I found that a string defined in at the base Console level will come back as:

Node.Expression.NodeType == ExpressionType.Constant

The Node.Expression passes back properties of:

CanReduce = false
DebugView = ".Constant<ConsoleApplication1.Program+<>c__DisplayClass1>(ConsoleApplication1.Program+<>c__DisplayClass1)"
NodeType = Constant
Type = System.Type {System.RunetimeType}
Value = {ConsoleApplication1.Program}

The Node.Expression.Value contains:

testValue = "Test" (Type: string)

How do I get this value? I've tried several things, like:

var memberType = node.Expression.Type.DeclaringType;

This passes back a ConsoleApplication1.Program type.

However, when I do:

 memberType.GetProperty("testValue");   // Declaring Type from Expression

It passes back null.

The above methods work fine if I place the lambda "strings" in a class, but doesn't work if they string is defined in the console function.

Can anyone tell me how to get the string value if it's defined at the function level of the lambda?

EDITED: Added VisitMember

protected override Expression VisitMember(MemberExpression node)
{
    if (node.NodeType == ExpressionType.Constant)
    {
        // Node.Expression is a ConstantExpression type.
        // node.Expression contains properties above
        // And Has Value of:  {ConsoleApplication1.Program}
        // Expanding Value in Watch window shows:  testValue = "Test"

        // How do I get this value, if the ConsoleApplication1.Program type doesn't
        // even know about it?  Looks like maybe a dynamic property?
    }
 }

EDITED

Added code to the console app example to show what works and what doesn't.

回答1:

The lambda in your example has "closed over" the testValue variable, meaning the compiler has captured it as a field of the same name in an automatically generated class called ConsoleApplication1.Program+<>c__DisplayClass1>. You can use normal reflection to get the current value of that field by casting the right hand-side of the binary expression into a MemberExpression.

var testValue = "hello";
var expr = (Expression<Func<string, bool>>) (x => x == testValue);
var rhs = (MemberExpression) ((BinaryExpression) expr.Body).Right;
var obj = ((ConstantExpression) rhs.Expression).Value;
var field = (FieldInfo) rhs.Member;
var value = field.GetValue(obj);
Debug.Assert(Equals(value, "hello"));
testValue = "changed";
value = field.GetValue(obj);
Debug.Assert(Equals(value, "changed"));

Alternatively you can change your variable into a constant.

const string testValue = "hello";
var expr = (Expression<Func<string, bool>>) (x => x == testValue);
var value = ((ConstantExpression) ((BinaryExpression) expr.Body).Right).Value;
Debug.Assert(Equals(value, "hello"));


回答2:

Instead of doing this by yourself, have a look at PartialEvaluator from Matt Warren. It replaces all references to constants with the constants themselves.