Squeak.ru - шаблоны программирования

Как создавать лямбда-выражения и добавлять их в действия с помощью отражения

Предположим, в С# у меня есть класс с произвольным количеством Actions, который может иметь любое количество общих аргументов:

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

И я регистрирую некоторые отладочные лямбда-выражения для экземпляра этого класса, которые просто выводят имя поля действия:

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

Я хотел бы автоматизировать создание этих отладочных лямбд с использованием отражения следующим образом:

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 
        }   
    }
}

Я могу получить желаемый результат для действий без аргументов — приведенный выше код действительно выводит "a was called" — но я не знаю, как это сделать для дженериков.

Я считаю, что знаю, что мне нужно делать, но не как:

  1. Используйте отражение, чтобы создать Action<T1, T2, ...> object, используя типы в actionArgTypes, которые обертывают вызов действия callback.
  2. Добавьте этот вновь созданный объект в Общее действие, указанное в поле.

Как мне сделать это или подобное, чтобы добиться желаемого эффекта от добавления такого обратного вызова отладки?


Ответы:


1

Вот довольно простая реализация с использованием 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 core. В реальном приложении должны быть проверки, кеширование и, возможно, целый сборщик сборок:

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);
    }
}
14.09.2018
  • Есть ли способ сделать это в С# 3.5? 18.09.2018
  • Я добавил пример, используя ILGenerator. 19.09.2018
  • Самостоятельно я понял, что, по крайней мере, для 3.5 исправление просто означало осознание того, что Expression.Parameter требует двух аргументов, поэтому я исправил его, используя: var parameters = actionArgTypes.Select( argType => Expression.Parameter(argType, null) ).ToArray();. иметь версию, которая работает еще более универсально, это здорово, так что спасибо! 19.09.2018
  • Новые материалы

    Угловая структура архитектуры
    Обратите внимание, что эта статья устарела, я решил создать новую с лучшей структурой и с учетом автономных компонентов: https://medium.com/@marekpanti/angular-standalone-architecture-b645edd0d54a..

    «Данные, которые большинство людей используют для обучения своих моделей искусственного интеллекта, поставляются со встроенным…
    Первоначально опубликовано HalkTalks: https://hacktown.com.br/blog/blog/os-dados-que-a-maioria-das-pessoas-usa-para-treinar-seus-modelos-de-inteligencia-artificial- ja-vem-com-um-vies-embutido/..

    Сильный ИИ против слабого ИИ: различия парадигм искусственного интеллекта
    В последние годы изучению и развитию искусственного интеллекта (ИИ) уделяется большое внимание и прогресс. Сильный ИИ и Слабый ИИ — две основные парадигмы в области искусственного интеллекта...

    Правильный способ добавить Firebase в ваш проект React с помощью React Hooks
    React + Firebase - это мощная комбинация для быстрого и безопасного создания приложений, от проверки концепции до массового производства. Раньше (знаете, несколько месяцев назад) добавление..

    Создайте API с помощью Python FastAPI
    Создание API с помощью Python становится очень простым при использовании пакета FastAPI. После установки и импорта вы можете создать приложение FastAPI и указать несколько конечных точек. Каждой..

    Веселье с прокси-сервером JavaScript
    Прокси-серверы JavaScript — это чистый сахар, если вы хотите создать некоторую общую логику в своих приложениях, чтобы облегчить себе жизнь. Вот один пример: Связь клиент-сервер Мы..

    Получить бесплатный хостинг для разработчиков | Разместите свой сайт за несколько шагов 🔥
    Статические веб-сайты — это веб-страницы с фиксированным содержанием и его постоянным содержанием. Но теперь статические сайты также обрабатывают динамические данные с помощью API и запросов...