为什么我不能表达身体转换成MethodCallExpression [英] Why can't I convert Expression Body to MethodCallExpression

查看:932
本文介绍了为什么我不能表达身体转换成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。





  1. 如何将表达式转换为适当的类型,以获得的MethodInfo 给定表达式的?

  2. 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'.

  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?

解决方案

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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆