7


介绍

我的应用程序使用方法链接实例化一个对象,因此它的生成和配置如下:

var car = new Car("Ferrari").Doors(2).OtherProperties(x = x.Color("Red"));


问题

我需要在运行时动态生成此对象 - 配置所需的链式方法将在运行时确定,因此必须动态组装所有内容。我过去曾使用反射来创建简单的对象,例如new Car("Ferrari", 2, "Red")- 我对此很满意 - 但从来没有使用包含 lambda 表达式作为参数的链式方法 - 这两个因素真的让我卡住了。我研究了表达式树,并相信这是创建动态表达式参数的解决方案的一部分,但我完全陷入了试图弄清楚如何将其与反射缝合在一起以创建基础对象和其他链接方法的过程中。


感谢和赞赏

提前花时间查看我的问题以及您可能提供的任何指导或信息。


更新:结论

非常感谢 dasblinkenlight 和 Jon Skeet 的回答。我选择了 dasblinkenlight 的答案,因为他的代码示例让我立即启动并运行。对于方法链接,我基本上在接受的答案中使用了相同的循环方法,所以我不会重复该代码,但下面是我编写的代码,用于将表达式树方法调用动态转换为操作委托,然后可以通过反射执行,Invoke()如dasblinkenlight 的回答。正如乔恩指出的那样,这确实是问题的症结所在。

帮助类来存储方法元数据。

public struct Argument
    {
        public string TypeName;
        public object Value;
    }

public class ExpressionTreeMethodCall
{
    public string MethodName { get; set; }
    public IList<Argument> Arguments { get; set; }

    public ExpressionTreeMethodCall()
    {
        Arguments = new List<Argument>();
    }
}


组装 lambda 表达式方法调用的静态方法,然后将其作为操作委托返回以在其他地方执行(Invoke()在我的情况下作为参数传递)。

public static Action<T> ConvertExpressionTreeMethodToDelegate<T>(ExpressionTreeMethodCall methodData)
    {            
        ParameterExpression type = Expression.Parameter(typeof(T));

        var arguments = new List<ConstantExpression>();
        var argumentTypes = new List<Type>();

        foreach (var a in methodData.Arguments)
        {
            arguments.Add(Expression.Constant(a.Value));
            argumentTypes.Add(Type.GetType(a.TypeName));
        }

        // Creating an expression for the method call and specifying its parameter.
        MethodCallExpression methodCall = Expression.Call(type, typeof(T).GetMethod(methodData.MethodName, argumentTypes.ToArray()), arguments);

        return Expression.Lambda<Action<T>>(methodCall, new[] { type }).Compile();
    }
4

2 回答 2

4

您面临两个不同的问题:

  • 调用链式方法,以及
  • 调用以 lambda 作为参数的方法

让我们分别处理这两个。

假设您有以下可用信息:

  • AConstructorInfo代表链中的第一项(构造函数)
  • 表示构造函数参数的对象数组
  • 一组MethodInfo对象 - 每个链接函数一个
  • 表示每个链接函数的参数的对象数组数组

那么构造结果的过程将如下所示:

ConstructorInfo constr = ...
object[] constrArgs = ...
MethodInfo[] chainedMethods = ...
object[][] chainedArgs = ...
object res = constr.Invoke(constrArgs);
for (int i = 0 ; i != chainedMethods.Length ; i++) {
    // The chaining magic happens here:
    res = chainedMethods[i].Invoke(res, chainedArgs[i]);
}

循环结束后,您将res包含一个已配置的对象。

上面的代码假设链式方法中没有泛型方法;如果某些方法碰巧是泛型的,则在调用Invoke.

现在让我们看看 lambda。根据传递给方法的 lambda 类型,您需要传递具有特定签名的委托。您应该能够使用System.Delegate类将方法转换为可调用的委托。您可能需要创建实现所需委托的支持方法。如果没有看到您必须能够通过反射调用的确切方法,很难说如何。您可能需要使用表达式树,并Func<...>在编译它们后获取实例。的调用x.Color("Red")如下所示:

Expression arg = Expression.Parameter(typeof(MyCarType));
Expression red = Expression.Constant("Red");
MethodInfo color = typeof(MyCarType).GetMethod("Color");
Expression call = Expression.Call(arg, color, new[] {red});
var lambda = Expression.Lambda(call, new[] {arg});
Action<MyCarType> makeRed = (Action<MyCarType>)lambda.Compile();
于 2012-10-27T22:23:57.500 回答
2

但绝不会使用包含 lambda 表达式作为参数的链式方法

好吧,链式方法就是一点。这只是多次使用反射的问题。串连起来

foo.X().Y()

你需要:

  • X从声明的类型中获取方法foo
  • 使用 的值foo作为调用目标,通过反射调用方法,并记住结果(例如tmp
  • 从返回类型Y声明类型中获取方法(参见)XMethodInfo.ReturnType
  • tmp使用之前的结果 ( ) 作为调用目标,通过反射调用方法

lambda 表达式更难——它实际上取决于首先如何为您提供表达式。使用表达式树然后调用来构建一个委托来执行一个合理任意的表达式并不难LambdaExpression.Compile,但是你需要知道你在做什么。

于 2012-10-27T21:57:39.590 回答