Reputation: 6790
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'.
MethodInfo
of the given expression?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
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
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