bflemi3
bflemi3

Reputation: 6790

Why can't I convert Expression Body to MethodCallExpression

Given the following class:

public class MyClass {

    private readonly UrlHelper _urlHelper;

    // constructor left out for brevity

    // this is one of many overloaded methods
    public ILinkableAction ForController<TController, T1, T2>(Expression<Func<TController, Func<T1, T2>>> expression) {
        return ForControllerImplementation(expression);
    }

    private ILinkableAction ForControllerImplementation<TController, TDelegate>(Expression<Func<TController, TDelegate>> expression) {
        var linkableMethod = new LinkableAction(_urlHelper);

        var method = ((MethodCallExpression) expression.Body).Method;
        method.GetParameters().ToList().ForEach(p => linkableMethod.parameters.Add(new Parameter {
            name = p.Name,
            parameterInfo = p
        }));

        return linkableMethod;
    }
}

and the following implementation:

var myClass = new MyClass(urlHelper);
myClass.ForController<EventsController, int, IEnumerable<EventDto>>(c => c.GetEventsById);

where GetEventsById has the signature:

IEnumerable<EventDto> GetEventsById(int id);

I'm getting the error:

Unable to cast object of type 'System.Linq.Expressions.UnaryExpression' to type 'System.Linq.Expressions.MethodCallExpression'.

  1. How can I convert the expression to the appropriate type to get the MethodInfo of the given expression?
  2. TDelegate, in the above example, is Func<int, IEnumerable<EventDto>> at runtime. So being that it's a Delegate why am I not able to get the MethodInfo from the expression?

Upvotes: 3

Views: 8761

Answers (2)

Paul Easter
Paul Easter

Reputation: 661

I think the answer is a lot simpler than all of this. The problem is your lambda expression signature is as such:

Expression<Func<TController, Func<T1, T2>>> expression

So you delegate signature is :

Func<TController, Func<T1, T2>>
or
Func<T1, T2> function(TController controller){}

That function takes a TController as input as returns a delegate (Func<T1, T2>); Whereas your implementation lambda that you are passing (c => c.GetEventsById) has a signature of:

Expression<Func<TController, T1>> expression

So your compiled lambda delegate signature is:

Func<EventsController, int> 
or
int function(EventsController controller){}

So you are getting a UnaryExpression in the body because it represents the delegate conversion (which I'm assuming would throw an exception if you tried to compile/invoke it -> Expression.Compile().Invoke()). Make your signatures match and your Expression Body will be a methodCallExpression.

Upvotes: 2

sircodesalot
sircodesalot

Reputation: 11439

The problem is that a MethodCallExpression has to actually be a method. Consider:

public static void Main()
{
    Express(str => str.Length);
    Console.ReadLine();
}


static void Express(Expression<Func<String, Int32>> expression)
{
    // Outputs: PropertyExpression (Which is a form of member expression)
    Console.WriteLine(expression.Body.GetType()); 
    Console.ReadLine();
}

Expressions are determined at compile time, which means when I say str => str.Length I'm calling a property on str and so the compiler resolves this to a MemberExpression.

If I instead change my lambda to look like this:

Express(str => str.Count());

Then the compiler understands that I'm calling Count() on str and so it resolves to a MethodCallExpression ... because it's actually a method.

Note though, that this means you can't really 'convert' expressions from one type to another, any more than you can 'convert' a String into an Int32. You can do a parse, but I think you get that that's not really a conversation...

...that said, you can BUILD a MethodCallExpression from nothing, which is helpful in some cases. For example, let's build the lambda:

(str, startsWith) => str.StartsWith(startsWith)


(1) First we need to start by building the two parameters: (str, startsWith) => ...

// The first parameter is type "String", and well call it "str"
// The second parameter also type "String", and well call it "startsWith"
ParameterExpression str = Expression.Parameter(typeof(String), "str");
ParameterExpression startsWith = Expression.Parameter(typeof(String), "startsWith");


(2) Then on the right hand side, we need to build: str.StartsWith(startsWith). First we need to use reflection to bind to the StartsWith(...) method of String that takes a single input of type String, like so:

// Get the method metadata for "StartsWith" -- the version that takes a single "String" input.
MethodInfo startsWithMethod = typeof(String).GetMethod("StartsWith", new [] { typeof(String) });


(3) Now that we have the binding-metadata, we can use a MethodCallExpression to actually call the method, like so:

//This is the same as (...) => str.StartsWith(startsWith);
// That is: Call the method pointed to by "startsWithMethod" bound above. Make sure to call it
// on 'str', and then use 'startsWith' (defined above as well) as the input.
MethodCallExpression callStartsWith = Expression.Call(str, startsWithMethod, new Expression[] { startsWith });


(4) Now we have the left side (str, startsWith) / and the right side str.StartsWith(startsWith). Now we just need to join them into one lambda. Final code:

// The first parameter is type "String", and well call it "str"
// The second parameter also type "String", and well call it "startsWith"
ParameterExpression str = Expression.Parameter(typeof(String), "str");
ParameterExpression startsWith = Expression.Parameter(typeof(String), "startsWith");

// Get the method metadata for "StartsWith" -- the version that takes a single "String" input.
MethodInfo startsWithMethod = typeof(String).GetMethod("StartsWith", new[] { typeof(String) });

// This is the same as (...) => str.StartsWith(startsWith);
// That is: Call the method pointed to by "startsWithMethod" bound above. Make sure to call it
// on 'str', and then use 'startsWith' (defined above as well) as the input.
MethodCallExpression callStartsWith = Expression.Call(str, startsWithMethod, new Expression[] { startsWith });

// This means, convert the "callStartsWith" lambda-expression (with two Parameters: 'str' and 'startsWith', into an expression
// of type Expression<Func<String, String, Boolean>
Expression<Func<String, String, Boolean>> finalExpression =
    Expression.Lambda<Func<String, String,  Boolean>>(callStartsWith, new ParameterExpression[] { str, startsWith });

// Now let's compile it for extra speed!
Func<String, String, Boolean> compiledExpression = finalExpression.Compile();

// Let's try it out on "The quick brown fox" (str) and "The quick" (startsWith)
Console.WriteLine(compiledExpression("The quick brown fox", "The quick")); // Outputs: "True"
Console.WriteLine(compiledExpression("The quick brown fox", "A quick")); // Outputs: "False"

Update Well, maybe something like this might work:

class Program
{
        public void DoAction()
        {
            Console.WriteLine("actioned");
        }

        public delegate void ActionDoer();

        public void Do()
        {
            Console.ReadLine();
        }

        public static void Express(Expression<Func<Program, ActionDoer>> expression)
        {
            Program program = new Program();
            Func<Program, ActionDoer> function = expression.Compile();
            function(program).Invoke();
        }

        [STAThread]
        public static void Main()
        {
            Express(program => program.DoAction);
            Console.ReadLine();
        }
}

Update: Came across something on accident. Consider this code:

    public static String SetPropertyChanged<T>(Expression<Func<T, Object>> expression)
    {
        UnaryExpression convertExpression = (UnaryExpression)expression.Body;
        MemberExpression memberExpression = (MemberExpression)convertExpression.Operand;
        return memberExpression.Member.Name;

        ...
    }

The input is a simple lambda for WPF:

base.SetPropertyChanged(x => x.Visibility);

since I'm projecting into an Object, I noticed that visual studio converts this into a UnaryExpression, which I think is the same problem you're running into. If you put a break-point and examine the actual expression (in my case) it says x => Convert(x.Visibility). The problem is the Convert (which is effectively just a cast to a currently unknown type). All you have to do is remove it (as I do in the code above by using the Operand member, and you should be all set. Maybe you'll have your MethodCallExpression.

Upvotes: 8

Related Questions