给定以下类:
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;
}
}
和以下实现:
var myClass = new MyClass(urlHelper);
myClass.ForController<EventsController, int, IEnumerable<EventDto>>(c => c.GetEventsById);
其中GetEventsById
具有签名:
IEnumerable<EventDto> GetEventsById(int id);
我得到错误:
无法强制转换'System.Linq.Expressions '类型的对象。UnaryExpression' to type ' system . linq . expressions . methodcalexpression '.
- 我如何将表达式转换为适当的类型以获得给定表达式的
MethodInfo
? 在上述示例中, -
TDelegate
在运行时为Func<int, IEnumerable<EventDto>>
。所以,这是一个Delegate
,为什么我不能得到MethodInfo
从表达式?
问题是MethodCallExpression
实际上必须是一个方法。考虑:
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();
}
表达式是在编译时确定的,这意味着当我说str => str.Length
时,我在str
上调用属性,因此编译器将其解析为MemberExpression
。
如果我把lambda改成这样:
Express(str => str.Count());
然后编译器理解我在str
上调用Count()
,因此它解析为MethodCallExpression
…因为它实际上是一个方法
请注意,这意味着你不能真正地将表达式从一种类型"转换"到另一种类型,就像你不能将String
"转换"到Int32
一样。你可以做一个解析,但我认为你明白这不是一个真正的对话…
…也就是说,您可以从零开始构建MethodCallExpression
,这在某些情况下很有帮助。例如,让我们构建lambda:
(str, startsWith) => str.StartsWith(startsWith)
(1)首先,我们需要从构建两个参数开始:(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)然后在右侧,我们需要构建:str.StartsWith(startsWith)
。首先,我们需要使用反射绑定到String
的StartsWith(...)
方法,该方法接受String
类型的单个输入,如下所示:
// Get the method metadata for "StartsWith" -- the version that takes a single "String" input.
MethodInfo startsWithMethod = typeof(String).GetMethod("StartsWith", new [] { typeof(String) });
(3)现在我们有了绑定元数据,我们可以使用MethodCallExpression
来实际调用该方法,如下所示:
//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 });
现在我们有左边的(str, startsWith)
/和右边的str.StartsWith(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");
// 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"
好吧,也许像这样可以工作:
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();
}
}
:偶然发现了一些东西。考虑以下代码:
public static String SetPropertyChanged<T>(Expression<Func<T, Object>> expression)
{
UnaryExpression convertExpression = (UnaryExpression)expression.Body;
MemberExpression memberExpression = (MemberExpression)convertExpression.Operand;
return memberExpression.Member.Name;
...
}
输入是WPF的一个简单lambda:
base.SetPropertyChanged(x => x.Visibility);
因为我正在投射到Object
,我注意到visual studio将其转换为UnaryExpression
,我认为这是你遇到的同样的问题。如果您放置一个断点并检查实际的表达式(在我的例子中),它会显示为x => Convert(x.Visibility)
。问题是Convert
(它实际上只是转换为当前未知的类型)。您所要做的就是删除它(就像我在上面的代码中使用Operand
成员所做的那样),然后您应该都设置好了。也许你会有你的MethodCallExpression
。
我认为答案要比这一切简单得多。问题是您的lambda表达式签名如下:
Expression<Func<TController, Func<T1, T2>>> expression
你的委托签名是:
Func<TController, Func<T1, T2>>
or
Func<T1, T2> function(TController controller){}
这个函数接受一个TController作为输入,返回一个委托(Func<T1, T2>
);而您正在传递(c => c.GetEventsById)
的实现lambda具有签名:
Expression<Func<TController, T1>> expression
编译后的lambda委托签名是:
Func<EventsController, int>
or
int function(EventsController controller){}
所以你在体内得到一个UnaryExpression,因为它代表委托转换(我假设会抛出一个异常,如果你试图编译/调用它-> Expression.Compile().Invoke()
)。让你的签名匹配,你的表达式体将是一个methodCallExpression。