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

mma*_*007 6 .net c# reflection lambda expression


介绍

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

var car = new Car("Ferrari").Doors(2).OtherProperties(x = x.Color("Red"));
Run Code Online (Sandbox Code Playgroud)


问题

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


谢谢和赞赏

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


更新:结论

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

Helper类存储方法元数据.

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>();
    }
}
Run Code Online (Sandbox Code Playgroud)


用于组装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();
    }
Run Code Online (Sandbox Code Playgroud)

das*_*ght 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]);
}
Run Code Online (Sandbox Code Playgroud)

循环结束后,您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();
Run Code Online (Sandbox Code Playgroud)