Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get value from a ConstantExpression

I'm looking to get a value from an

var guid = Guid.Parse("SOMEGUID-GUID-GUID-GUID-SOMEGUIDGUID");
Expression<Func<Someobject, bool>> selector = x => x.SomeId == guid;

For logging purposes I need to be able to fish out that guid.

I tried the following code, which I feel is somewhat close to what I'm looking for, but not quite.

BinaryExpression binaryExpression = (BinaryExpression)selector.Body;
MemberExpression memberExpression = (MemberExpression)((UnaryExpression)binaryExpression.Right).Operand;
ConstantExpression constantExpression = (ConstantExpression)memberExpression.Expression;

Now, ConstantExpression exposes a member 'Value', which does contain what I'm looking for, but I'm a bit puzzled how to actually extract this.

And no:

var val = (Guid)constantExpression.Value; 

Does not work :)

SOLVED

The end result looks like:

BinaryExpression binaryExpression = (BinaryExpression)selector.Body;
MemberExpression memberExpression = (MemberExpression)((UnaryExpression)binaryExpression.Right).Operand;
var myGuid = Expression.Lambda(memberExpression).Compile().DynamicInvoke();

Follow-up

I did some rudementary speed testing using the following code:

    static void Main(string[] args)
    {
        var id = Guid.Parse("bleh");

        Expression<Func<Thingemebob, bool>> selector = x => x.Id == id;

        var tickList = new List<long>();

        for (int i = 0; i < 100000; i++)
        {
            var sw = Stopwatch.StartNew();
            GetValueWithExpressionsAndReflection(selector);
            sw.Stop();
            tickList.Add(sw.ElapsedTicks);
        }

                    Trace.WriteLine("GetValueWithExpressionsAndReflection: Average over 100000, first call included: " + tickList.Average());
        Trace.WriteLine("GetValueWithExpressionsAndReflection: First call: " + tickList[0]);
        Trace.WriteLine("GetValueWithExpressionsAndReflection: Average over 100000, first call excluded: " + tickList.Skip(1).Average());

        tickList = new List<long>();

        for (int i = 0; i < 100000; i++)
        {
            var sw = Stopwatch.StartNew();
            GetValueWithCompiledExpression(selector);
            sw.Stop();
            tickList.Add(sw.ElapsedTicks);
        }

                    Trace.WriteLine("GetValueWithCompiledExpression: Average over 100000, first call included: " + tickList.Average());
        Trace.WriteLine("GetValueWithCompiledExpression: First call: " + tickList[0]);
        Trace.WriteLine("GetValueWithCompiledExpression: Average over 100000, first call excluded: " + tickList.Skip(1).Average());

        Debugger.Break();
    }

    private static void GetValueWithCompiledExpression(Expression<Func<Note, bool>> selector)
    {
        BinaryExpression binaryExpression = (BinaryExpression)selector.Body;
        MemberExpression memberExpression = (MemberExpression)((UnaryExpression)binaryExpression.Right).Operand;
        var o = Expression.Lambda(memberExpression).Compile().DynamicInvoke();
    }

    private static void GetValueWithExpressionsAndReflection(Expression<Func<Note, bool>> selector)
    {
        BinaryExpression binaryExpression = (BinaryExpression)selector.Body;
        MemberExpression memberExpression = (MemberExpression)((UnaryExpression)binaryExpression.Right).Operand;
        ConstantExpression constantExpression = (ConstantExpression)memberExpression.Expression;

        FieldInfo member = (FieldInfo)memberExpression.Member;
        var instance = constantExpression.Value;
        var guid = member.GetValue(instance);
    }

Turns out the compile version is MUCH slower. We're looking at a huge difference. (Timing is in ticks):

GetValueWithExpressionsAndReflection: Average over 100000, first call included: 0,93122

GetValueWithExpressionsAndReflection: First call: 851

GetValueWithExpressionsAndReflection: Average over 100000, first call excluded: 0,922719227192272

Versus:

GetValueWithCompiledExpression: Average over 100000, first call included: 499,53669

GetValueWithCompiledExpression: First call: 16818

GetValueWithCompiledExpression: Average over 100000, first call excluded: 499,373503735037

Rudementary tests or not: no doubt I will be using the reflection version. My results seem to be consistent with: http://www.minddriven.de/index.php/technology/dot-net/c-sharp/efficient-expression-values

like image 367
Apeiron Avatar asked Aug 31 '15 10:08

Apeiron


1 Answers

Your const expression has type EFAndExpressions.Program+<>c__DisplayClass0. This means that the expression has the following structure:

var compilerGeneratedClass = new compilerGeneratedClass() {
   guid = Guid.Parse("SOMEGUID-GUID-GUID-GUID-SOMEGUIDGUID"); };
Expression<Func<Someobject, bool>> selector = x => x.SomeId == compilerGeneratedClass.guid;

The compiler does this for you. Use a decompiler to check out the details.

Now you know how the expression tree looks like and you can decompose it. You'll need to use reflection to obtain the runtime value of the compilerGeneratedClass.guid field or property.

This value is not part of the expression tree directly.

like image 70
usr Avatar answered Nov 09 '22 17:11

usr