Akash
Akash

Reputation: 2371

Accessing calling object from MethodCallExpression

I'm trying to learn about Expression trees, and I've created a method that takes an

Expression<Func<bool>> 

and executes it if it satisfies some conditions - see the code below.

        private static void TryCommand(Expression<Func<bool>> expression)
        {
            var methodCallExpression = expression.Body as MethodCallExpression;
            if (methodCallExpression == null)
            {
                throw new ArgumentException("expression must be a MethodCallExpression.");
            }

            if (methodCallExpression.Object.Type != typeof (MyClass))
            {
                throw new ArgumentException("expression must be operating on an instanceof MyClass.");                
            }

            var func = expression.Compile();
            var success = func.Invoke();

            if(!success)
            {
                Console.WriteLine(methodCallExpression.Method.Name + "() failed with error code " + (func.Target as MyClass).GetError());
            }
        }

The problem that

(func.Target as MyClass) 

is null. Clearly I'm doing something wrong! How do I access the instance that the method is operating on?

Upvotes: 1

Views: 3599

Answers (3)

Rob
Rob

Reputation:

The target is null because the method is static. In reflection Invoke(..) on a static MethodInfo will ignore the target. This is likely an extension method, in which case the first argument is the inferred target.

Since most of LINQ is based on extension methods you'll see this quite often going forward with reflection.

Upvotes: 1

Akash, once you have a MethodCallExpression it's simple to recover the method caller. You must recover the MemberExpression and build an Expression tree that evaluates it.

See the code below:


MethodCallExpression methodCallExpression = (MethodCallExpression)expression.Body;
MemberExpression memberExpression = (MemberExpression)methodCallExpression.Object;

Expression<Func<Object>> getCallerExpression = Expression<Func<Object>>.Lambda<Func<Object>>(memberExpression);
Func<Object> getCaller = getCallerExpression.Compile();
MyClass caller = (MyClass)getCaller();

Hope this helps,

Ricardo Lacerda Castelo Branco

Upvotes: 4

Jon Skeet
Jon Skeet

Reputation: 1500825

The target of the method call is an instance of MyClass, but the delegate itself isn't the method call. It's something which will perform the method call when it's executed.

If you look at func.Target, you'll see it's a System.Runtime.CompilerServices.ExecutionScope.

Now you could test for that, cast to it, and then fetch either the Locals or the Globals (not sure which) to get the target. However, I suspect it would be cleaner just to change to use a Func<int> (or whatever type your error code is) and return the error code when you execute the delegate in the first place. Then you wouldn't even need an expression tree.

EDIT: Given your comments, I'd suggest:

public static void TryCommand(Expression<Func<MyClass,bool>> command,
                              MyClass c)
{
    // Code as before to find the method name etc.

    Func<MyClass, bool> compiled = command.Compile();

    if (!compiled(c))
    {
        Console.WriteLine(methodCallExpression.Method.Name
            + "() failed with error code " + c.GetError());
    }
}

You'd then call it with:

TryCommand(x => x.SomeMethod(), myClass);

Upvotes: 1

Related Questions