对于您的两个示例,这实际上可以通过两个表达式访问者来完成(代码已注释):
static class Extensions {
public static TResult FakeInvoke<TResult>(this Delegate instance, params object[] parameters)
{
// this is not intended to be called directly
throw new NotImplementedException();
}
public static TExpression Unwrap<TExpression>(this TExpression exp) where TExpression : Expression {
return (TExpression) new FakeInvokeVisitor().Visit(exp);
}
class FakeInvokeVisitor : ExpressionVisitor {
protected override Expression VisitMethodCall(MethodCallExpression node) {
// replace FakeInvoke call
if (node.Method.Name == "FakeInvoke") {
// first obtain reference to method being called (so, for c.FakeInvoke(...) that will be "c")
var func = (Delegate)Expression.Lambda(node.Arguments[0]).Compile().DynamicInvoke();
// explore method argument names and types
var argumentNames = new List<string>();
var dummyArguments = new List<object>();
foreach (var arg in func.Method.GetParameters()) {
argumentNames.Add(arg.Name);
// create default value for each argument
dummyArguments.Add(arg.ParameterType.IsValueType ? Activator.CreateInstance(arg.ParameterType) : null);
}
// now, invoke function with default arguments to obtain expression (for example, this one () => a*(a + 3)).
// all arguments will have default value (0 in this case), but they are not literal "0" but a reference to "a" member with value 0
var exp = (Expression) func.DynamicInvoke(dummyArguments.ToArray());
// this is expressions representing what we passed to FakeInvoke (for example expression (x + 3))
var argumentExpressions = (NewArrayExpression)node.Arguments[1];
// now invoke second visitor
exp = new InnerFakeInvokeVisitor(argumentExpressions, argumentNames.ToArray()).Visit(exp);
return ((LambdaExpression)exp).Body;
}
return base.VisitMethodCall(node);
}
}
class InnerFakeInvokeVisitor : ExpressionVisitor {
private readonly NewArrayExpression _args;
private readonly string[] _argumentNames;
public InnerFakeInvokeVisitor(NewArrayExpression args, string[] argumentNames) {
_args = args;
_argumentNames = argumentNames;
}
protected override Expression VisitMember(MemberExpression node) {
// if that is a reference to one of our arguments (for example, reference to "a")
if (_argumentNames.Contains(node.Member.Name)) {
// find related expression
var idx = Array.IndexOf(_argumentNames, node.Member.Name);
var argument = _args.Expressions[idx];
var unary = argument as UnaryExpression;
// and replace it. So "a" is replaced with expression "x + 3"
return unary?.Operand ?? argument;
}
return base.VisitMember(node);
}
}
}
可以这样使用:
Func<int, Expression<Func<int>>> c = (int a) => () => a * (a + 3);
Expression<Func<int, int>> d = (x) => 2 + c.FakeInvoke<int>(3 + x);
d = d.Unwrap(); // this is now "x => (2 + ((3 + x) * ((3 + x) + 3)))"
简单案例:
Func<int, Expression<Func<int>>> c = (int a) => () => a + 3;
Expression<Func<int>> d = () => 2 + c.FakeInvoke<int>(3);
d = d.Unwrap(); // this is now "() => 2 + (3 + 3)
有多个参数:
Func<int, int, Expression<Func<int>>> c = (int a, int b) => () => a * (a + 3) + b;
Expression<Func<int, int>> d = (x) => 2 + c.FakeInvoke<int>(3 + x, x + 5);
d = d.Unwrap(); // "x => (2 + (((3 + x) * ((3 + x) + 3)) + (x + 5)))"
请注意,FakeInvoke 不是类型安全的(您应该显式设置返回类型和参数,而不是检查)。但这只是举例,在实际使用中你可以创建很多 FakeInvoke 的重载,像这样:
public static TResult FakeInvoke<TArg, TResult>(this Func<TArg, Expression<Func<TResult>>> instance, TArg argument) {
// this is not intended to be called directly
throw new NotImplementedException();
}
上面的代码应该稍作修改以正确处理此类调用(因为参数现在不在单个 NewArrayExpression 中),但这很容易做到。有了这样的重载,你可以这样做:
Expression<Func<int, int>> d = (x) => 2 + c.FakeInvoke(3 + x); // this is type-safe now, you cannot pass non-integer as "3+x", nor you can pass more or less arguments than required.