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




简介

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

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();
}

您面临两个独立的问题:

  • 调用链式方法,以及
  • 调用以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();

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

好吧,链式方法就是比特。这只是多次使用反射的问题。将链接在一起

foo.X().Y()

你需要:

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

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

最新更新