如何使用反射创建 lambda 并将它们添加到操作中

How to create lambdas and add them to actions using reflection

假设在 C# 中我有 class,它有任意数量的 Actions,它可以有任意数量的泛型参数:

public class Container
{
    public Action a;
    public Action<float> b;
    public Action<int, float> c;
    // etc...
}

我正在这个 class 的实例上注册一些调试 lambdas,它只打印出操作字段的名称:

public static void Main()
{
    Container container = new Container();

    container.a += () => Console.WriteLine("a was called");
    container.b += (temp1) => Console.WriteLine("b was called");
    container.c += (temp1, temp2) => Console.WriteLine("c was called");

    container.a();
    container.b(1.5f);
    container.c(1, 1.5f);
}

我想使用反射自动创建这些调试 lambda,如下所示:

public static void Main()
{
    Container container = new Container();

    GenerateDebug(container);

    if(container.a != null) container.a();
    if(container.b != null) container.b(1.5f);
    if(container.c != null) container.c(1, 1.5f);
}

public static void GenerateDebug(Container c)
{
    Type t = c.GetType();
    FieldInfo[] fields = t.GetFields(BindingFlags.Instance | BindingFlags.Public);
    foreach(FieldInfo field in fields)
    {
        Action callback = () => Console.WriteLine(field.Name + " was called");

        Type[] actionArgTypes = field.FieldType.GetGenericArguments();
        if(actionArgTypes.Length == 0)
        {
            Action action = field.GetValue(c) as System.Action;
            action += callback;
            field.SetValue(c, action);
        }
        else
        {
            // 1. Create an Action<T1, T2, ...> object that takes the types in 'actionArgTypes' which wraps the 'callback' action
            // 2. Add this new lambda to the current Action<T1, T2, ...> field 
        }   
    }
}

我能够在没有参数的情况下为 Actions 获得所需的结果 - 上面的代码确实打印出来 "a was called" - 但我不知道如何为泛型执行此操作。

我相信我知道我需要做什么,只是不知道怎么做:

  1. 使用反射创建一个 Action<T1, T2, ...> object 使用 actionArgTypes 中的类型,它包装对 callback 操作的调用。
  2. 将这个新创建的对象添加到字段指定的通用操作中。

我将如何着手执行此操作或类似的操作以达到添加此类调试回调的预期效果?

这是一个使用 Expressions 的相当简单的实现,可以直接使用 ILGenerator,但在这种情况下不值得这么麻烦。

public static void GenerateDebug(Container c)
{
    Type t = c.GetType();
    FieldInfo[] fields = t.GetFields(BindingFlags.Instance | BindingFlags.Public);
    foreach(FieldInfo field in fields)
    {
        var fieldName = field.Name;
        Type[] actionArgTypes = field.FieldType.GetGenericArguments();
        // Create paramter expression for each argument
        var parameters = actionArgTypes.Select(Expression.Parameter).ToArray();
        // Create method call expression with a constant argument
        var writeLineCall = Expression.Call(typeof(Console).GetMethod("WriteLine", new [] {typeof(string)}), Expression.Constant(fieldName + " was called"));
        // Create and compile lambda using the fields type
        var lambda = Expression.Lambda(field.FieldType, writeLineCall, parameters);
        var @delegate = lambda.Compile();
        var action = field.GetValue(c) as Delegate;
        // Combine and set delegates
        action = Delegate.Combine(action, @delegate);
        field.SetValue(c, action);
    }
}

这是使用 ILGenerator 的相同功能,它应该适用于 .net Framework 2.0+ 以及 .net 核心。在现实生活中的应用程序中,应该有检查、缓存和可能的整个程序集生成器:

public static void GenerateDebug(Container c)
{
    Type t = c.GetType();
    FieldInfo[] fields = t.GetFields(BindingFlags.Instance | BindingFlags.Public);
    foreach(FieldInfo field in fields)
    {
        var fieldName = field.Name;
        Type[] actionArgTypes = field.FieldType.GetGenericArguments();

        var dm = new DynamicMethod(fieldName, typeof(void), actionArgTypes);
        var il = dm.GetILGenerator();
        il.Emit(OpCodes.Ldstr, fieldName + " was called using ilgen");
        il.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new [] {typeof(string)}));
        il.Emit(OpCodes.Ret);

        var @delegate = dm.CreateDelegate(field.FieldType);
        var action = field.GetValue(c) as Delegate;
        // Combine and set delegates
        action = Delegate.Combine(action, @delegate);
        field.SetValue(c, action);
    }
}