3

给定以下课程:

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”类型的对象转换为“System.Linq.Expressions.MethodCallExpression”类型。

  1. 如何将表达式转换为适当的类型以获取MethodInfo给定表达式的?
  2. TDelegate,在上面的例子中,是Func<int, IEnumerable<EventDto>>在运行时。因此,这是Delegate为什么我无法MethodInfo从表达式中获得?
4

2 回答 2

8

问题是 aMethodCallExpression必须实际上是一种方法。考虑:

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());

然后编译器知道我正在调用Count()str所以它解析为MethodCallExpression... 因为它实际上是一个方法。

但是请注意,这意味着您不能真正将表达式从一种类型“转换”为另一种类型,就像您不能将 aString转换为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). 首先,我们需要使用反射绑定到接受单个输入 type的StartsWith(...)方法,如下所示:StringString

// Get the method metadata for "StartsWith" -- the version that takes a single "String" input.
MethodInfo startsWithMethod = typeof(String).GetMethod("StartsWith", new [] { typeof(String) });


(3) 现在我们有了绑定元数据,我们可以使用 aMethodCallExpression来实际调用方法,如下所示:

//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) 现在我们有左侧(str, startsWith)/ 和右侧str.StartsWith(startsWith)。现在我们只需要将它们加入到一个 lambda 中。最终代码:

// 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);

因为我正在投影到 aObject中,所以我注意到 Visual Studio 将其转换为 a UnaryExpression,我认为这与您遇到的问题相同。如果您放置一个断点并检查实际表达式(在我的情况下),它会显示x => Convert(x.Visibility). 问题是Convert(实际上只是对当前未知类型的强制转换)。您所要做的就是删除它(就像我在上面的代码中使用该Operand成员所做的那样,您应该已经准备就绪。也许您将拥有您的MethodCallExpression.

于 2013-05-30T13:28:47.440 回答
2

我认为答案比所有这些都简单得多。问题是您的 lambda 表达式签名是这样的:

Expression<Func<TController, Func<T1, T2>>> expression

所以你委托签名是:

Func<TController, Func<T1, T2>>
or
Func<T1, T2> function(TController controller){}

该函数将 TController 作为输入作为返回委托 ( Func<T1, T2>);而您传递的实现 lambda(c => c.GetEventsById)具有以下签名:

Expression<Func<TController, T1>> expression

所以你编译的 lambda 委托签名是:

Func<EventsController, int> 
or
int function(EventsController controller){}

因此,您在正文中获得了 UnaryExpression,因为它表示委托转换(我假设如果您尝试编译/调用它会引发异常-> Expression.Compile().Invoke())。使您的签名匹配,您的表达式主体将是一个 methodCallExpression。

于 2014-09-28T00:26:43.373 回答