使用反射,链式方法和lambda表达式动态创建对象

时间:2012-10-27 21:52:46

标签: c# .net reflection lambda expression


简介

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

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


问题

我需要在运行时动态生成此对象 - 配置所需的链接方法将在运行时确定,因此必须动态地动态组装所有内容。我过去曾使用反射来创建像new Car("Ferrari", 2, "Red")这样的简单对象 - 我很酷 - 但从来没有任何包含lambda表达式作为参数的链式方法 - 这两个因素确实让我陷入困境。我已经查看了表达式树,并认为这是创建动态表达式参数的解决方案的一部分,但我完全陷入困境,试图弄清楚如何将它与反射结合在一起以创建基础对象和其他链式方法。


感谢和赞赏

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


更新:结论

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

用于存储方法元数据的助手类。

Invoke()


用于组装lambda表达式方法调用的静态方法,然后将其作为要在其他位置执行的操作委托返回(在我的情况下作为参数传递给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>(); } } )。

Invoke()

2 个答案:

答案 0 :(得分:2)

您面临两个不同的问题:

  • 调用链式方法,
  • 调用将lambdas作为参数的方法

让我们分别处理这两个问题。

假设您有以下信息:

  • 表示链中第一项(构造函数)的ConstructorInfo
  • 表示构造函数参数的对象数组
  • 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之前,需要在制作泛型方法的可调用实例时采取额外步骤。

现在让我们来看看lambdas。根据传递给方法的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();

答案 1 :(得分:1)

  

但从来没有任何包含lambda表达式作为参数的链式方法

嗯,链式方法是有点的。这只是几次使用反射的问题。连在一起

foo.X().Y()

你需要:

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

lambda表达式更难 - 它实际上取决于你将如何首先提供表达式。使用expression trees然后调用LambdaExpression.Compile来构建委托以执行合理任意的表达式并不太难,但您需要知道自己在做什么。