问题在于
MethodCallExpression
必须实际上是一个方法。考虑以下代码:
public static void Main()
{
Express(str => str.Length);
Console.ReadLine();
}
static void Express(Expression<Func<String, Int32>> 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) => ...
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
来实际调用方法,像这样:
MethodCallExpression callStartsWith = Expression.Call(str, startsWithMethod, new Expression[] { startsWith });
(4) 现在我们有了左侧的 (str, startsWith)
和右侧的 str.StartsWith(startsWith)
。现在我们只需要将它们合并成一个 lambda 表达式。最终代码:
ParameterExpression str = Expression.Parameter(typeof(String), "str");
ParameterExpression startsWith = Expression.Parameter(typeof(String), "startsWith");
MethodInfo startsWithMethod = typeof(String).GetMethod("StartsWith", new[] { typeof(String) });
MethodCallExpression callStartsWith = Expression.Call(str, startsWithMethod, new Expression[] { startsWith });
Expression<Func<String, String, Boolean>> finalExpression =
Expression.Lambda<Func<String, String, Boolean>>(callStartsWith, new ParameterExpression[] { str, startsWith });
Func<String, String, Boolean> compiledExpression = finalExpression.Compile();
Console.WriteLine(compiledExpression("The quick brown fox", "The quick"));
Console.WriteLine(compiledExpression("The quick brown fox", "A quick"));
更新
好的,也许这样做会有所帮助:
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
。