为什么我不能将表达式主体转换为方法调用表达式



给定以下类:

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

  1. 我如何将表达式转换为适当的类型以获得给定表达式的MethodInfo ?
  2. 在上述示例中,
  3. 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)。首先,我们需要使用反射绑定到StringStartsWith(...)方法,该方法接受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。

相关内容

  • 没有找到相关文章

最新更新