3

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?
1
  • See my answer below. The issue is that your delegate signatures do not match. So the expression body is represented as a Unary (conversion). Commented Oct 19, 2015 at 19:48

2 Answers 2

8

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.

Sign up to request clarification or add additional context in comments.

17 Comments

So if I understand correctly, since I'm not calling the method (ie: getEventById()) the compiler doesn't know that this is in fact a method and instead treats it as a UnaryExpression? How then would I get the MethodInfo of the method I've passed in the expression?
If it is in fact a method, then just use the lambda m => m.Method(). Make sure to use the ().
The point of this class and method is to be able to pass a strongly typed pointer to a method. I'm not actually calling the method, just getting the parameters that the method requires. By passing m => m.Method() as the expression I would need to also pass the arguments in the lambda but there are no arguments to pass at this point. I suppose I could pass the defaults of the arguments, but it defeats the purpose of declaring the type parameters in the ForController method signature. Does that make sense?
I figured you were getting at something like that, so what you'll have to do is use reflection to bind to the actual method you want, and then construct a MethodCallExpression/Compile it yourself. The example shows how to do this. Work the example once, and then it should be fairly straightforward how to do it.
reading back through this... excellent answer. Thank you again for taking the time to go through this with me!!
|
2

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.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.