为什么我不能表达身体转换成MethodCallExpression [英] Why can't I convert Expression Body to MethodCallExpression
问题描述
公共类MyClass的{
私人只读UrlHelper _urlHelper;
//构造排除在外为简洁
//这是许多重载的方法之一
公共ILinkableAction ForController< TController,T1,T2>(表达式来;函数功能:LT ; TController,Func键< T1,T2>>>表达式){
返回ForControllerImplementation(表达);
}
私人ILinkableAction ForControllerImplementation< TController,TDelegate>(表达式来; Func键< TController,TDelegate>>表达式){
变种linkableMethod =新LinkableAction(_urlHelper);
VAR方法=((MethodCallExpression)expression.Body)。方法;
method.GetParameters()了ToList()的ForEach(p值=方式>。linkableMethod.parameters.Add(新参数{
名称= p.Name,
的ParameterInfo =对
}));
返回linkableMethod;
}
}
和下面的实现:
VAR MyClass的=新MyClass的(urlHelper);
myClass.ForController< EventsController,INT,IEnumerable的< EventDto>>(C => c.GetEventsById);
其中, GetEventsById
有签名:
的IEnumerable< EventDto> GetEventsById(中间体ID);
我收到以下错误:
<块引用>
无法转换类型'System.Linq.Expressions.UnaryExpression对象键入'System.Linq.Expressions.MethodCallExpression。
块引用>
- 如何将表达式转换为适当的类型,以获得
的MethodInfo
给定表达式的? -
TDelegate
,在上面的例子中,是Func键< INT,IEnumerable的< EventDto>>
在运行时。所以是它是一个代表
为什么我不能够获得的MethodInfo
从表情?
的问题是,一个 MethodCallExpression
具有实际是一个方法。试想一下:
公共静态无效的主要()
{
快递(海峡=> str.Length );
到Console.ReadLine();
}
静态无效快递(表达式来; Func键<字符串的Int32>>表达式)
{
//输出:PropertyExpression(这是成员的一种表现形式)
Console.WriteLine(expression.Body.GetType());
到Console.ReadLine();
}
表达式是在编译时确定的,这意味着,当我说海峡=> str.Length
我调用的属性的关于 STR
键,所以编译器解析给 MemberExpression
。
如果我不是改变我的lambda看起来像这样:
快递(海峡=> str.Count());
那么编译器明白,我打电话 COUNT()
在 STR
,所以它解析为 MethodCallExpression
...因为它实际上是一个方法。
请注意,虽然,这意味着你不能真正'转换'从一种类型的表达式到另一个,任何比你更可以'转换'一个字符串
到的Int32
。你可以做一个分析,但我觉得你得到的是不是一个真正的谈话...
......这么说,你可以建立一个 MethodCallExpression
从一无所有,这在某些情况下,很有帮助。例如,让我们建立拉姆达:
(STR,startsWith)=> str.StartsWith(startsWith)
(1)首先,我们需要通过建立两个参数启动:(STR,startsWith)=> ...
//第一个参数的类型是字符串,且还有称它为 STR
//第二个参数还可以键入字符串,且还有称之为startsWith
ParameterExpression海峡= Expression.Parameter(typeof运算(字符串),STR);
ParameterExpression startsWith = Expression.Parameter(typeof运算(字符串),startsWith);
(2)然后在右手侧,我们需要打造: str.StartsWith(startsWith)
。首先,我们需要使用反射来绑定到字符串
的 StartsWith(...)
方法,它采用一个单一的输入类型字符串
,就像这样:
//获取元数据的方法对于StartsWith - 这需要一个单一的字符串输入的版本。
MethodInfo的startsWithMethod = typeof运算(字符串).GetMethod(StartsWith,新的[] {typeof运算(字符串)});
(3)既然我们有约束力的元数据,我们可以使用 MethodCallExpression
来实际调用该方法,像这样:
//这是一样的(...)=> str.StartsWith(startsWith);
//那就是:调用方法,通过startsWithMethod上述约束指出。确保叫它
//在'海峡',然后使用'startsWith(如上定义好)为输入。
MethodCallExpression callStartsWith = Expression.Call(STR,startsWithMethod,新的表达式[] {startsWith});
(4)现在我们有左侧(STR,startsWith)
/和右侧的 str.StartsWith(startsWith)
。现在我们只需要将它们连接成一个拉姆达。最后的代码:
//第一个参数的类型是字符串,且还有称之为STR
/ /第二个参数还可以键入字符串,且还有称之为startsWith
ParameterExpression海峡= Expression.Parameter(typeof运算(字符串),STR);
ParameterExpression startsWith = Expression.Parameter(typeof运算(字符串),startsWith);
//获取方法的元数据StartsWith - 这需要一个单一的字符串输入的版本。
MethodInfo的startsWithMethod = typeof运算(字符串).GetMethod(StartsWith,新的[] {typeof运算(字符串)});
//这是一样的(...)=> str.StartsWith(startsWith);
//那就是:调用方法,通过startsWithMethod上述约束指出。确保叫它
//在'海峡',然后使用'startsWith(如上定义好)为输入。
MethodCallExpression callStartsWith = Expression.Call(STR,startsWithMethod,新的表达式[] {startsWith});
//这意味着,转换callStartsWith的lambda表达式(有两个参数:'海峡'和'startsWith,入式表达式来的表现
//; Func键<弦乐,字符串,布尔值>
表达式来; Func键<字符串,字符串,布尔值>> finalExpression =
Expression.Lambda< Func键<字符串,字符串,布尔值>>(callStartsWith,新ParameterExpression [] {海峡,startsWith});
//现在让我们编译它已获得额外的速度
Func键<!字符串,字符串,布尔值> compiledExpression = finalExpression.Compile();
//让我们尝试一下的敏捷的棕色狐狸(STR)和快速(startsWith)
Console.WriteLine(compiledExpression(敏捷的棕色狐狸,快速)); / /输出:真
Console.WriteLine(compiledExpression(敏捷的棕色狐狸,快速)); //输出:假
更新
哦,也许这样的事情可能工作:
类节目
{
公共无效DoAction()
{
Console.WriteLine(付诸行动);
}
公众委托无效ActionDoer();
公共无效DO()
{
到Console.ReadLine();
}
公共静态无效快递(表达式来; Func键<程序,ActionDoer>>表达式)
{
计划项目=新计划();
Func键<程序,ActionDoer>功能= expression.Compile();
功能(程序).Invoke();
}
[STAThread]
公共静态无效的主要()
{
快递(程序= GT; program.DoAction);
到Console.ReadLine();
}
}
更新:
横跨东西来到事故。考虑下面的代码:
公共静态字符串SetPropertyChanged< T>(表达式来; Func键< T,对象>>表达式)
{
UnaryExpression convertExpression =(UnaryExpression)expression.Body;
MemberExpression memberExpression =(MemberExpression)convertExpression.Operand;
返回memberExpression.Member.Name;
:
}
输入是一个简单的拉姆达为WPF:
base.SetPropertyChanged(X => x.Visibility);
因为我伸入一个对象
我注意到的Visual Studio转换成 UnaryExpression
,我认为这是你正在运行到同样的问题这一点。如果你把一个破发点,并检查实际表达(对我来说)它说 X =>转换(x.Visibility)
。问题是转换
(这实际上是只是一个强制转换为当前未知类型)。所有你需要做的就是上面使用操作数
成员删除它(我的代码做的,你应该准备就绪。也许你有你的 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'.
- How can I convert the expression to the appropriate type to get the
MethodInfo
of the given expression? TDelegate
, in the above example, isFunc<int, IEnumerable<EventDto>>
at runtime. So being that it's aDelegate
why am I not able to get theMethodInfo
from the expression?
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
.
这篇关于为什么我不能表达身体转换成MethodCallExpression的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!