(Как) можно ли привязать / повторно привязать метод для работы с делегатом другой подписи?


Я разработчик c++, который использовал сигналы и слоты в c++, которые мне кажутся аналогичными делегатам в c#. Я обнаружил, что теряюсь в поисках функциональности, обеспечиваемой "bind", и чувствую, что мне чего-то не хватает.

Мне кажется, что что-то вроде следующего, что возможно в c++, должно быть возможно в c# с делегатами. Вот несколько psudo-кодов для того, что я буду делать в c++:

Slot<void> someCallback;

int foo(int i)
{
    std::cout << "Value: " << i << "n";
    return i;
}

int main()
{
    int i = 0;
    Slot<int> someCallback = bind( fun_ptr(foo), i );
    ++i; // added to show that late evaluation would be a non-trivial difference
    int result = someCallback();
    assert( result == 0 );
    return 0;
}

К сожалению, я не смог найти никаких ссылок на привязка / повторная привязка в отношении делегатов c#. Я что-то упустил? Есть ли какой-то радикально другой способ сделать это в c#?

2 11

2 ответа:

В C# мы делаем что-то вроде этого:

class Program {
    static Action Curry<T>(Action<T> action, T parameter) {
        return () => action(parameter);
    }

    static void Foo(int i) {
        Console.WriteLine("Value: {0}", i);
    }
    static void Main(string[] args) {
        Action curried = Curry(Foo, 5);
        curried();
    }
}

Очевидно, что метод Foo соответствует вашему методу Foo, только с соответствующими вызовами Console.WriteLine вместо std::cout.

Далее мы объявляем метод Curry, который принимает Action<T> и возвращает Action. В общем случае Action<T> - это делегат, который принимает один параметр типа T и возвращает void. В частности, Foo является Action<int>, поскольку он принимает один параметр типа int и возвращает void. Что касается возвращаемого типа Curry, он объявлен как Action. An Action - это делегат, который не имеет параметров и возвращает void.

Определение Curry весьма интересно. Мы определяем действие, используя лямбда-выражение, которое является очень особой формой анонимного делегата. Эффективно

() => action(parameter)

Говорит, что параметр void сопоставляется с параметром action, вычисляемым в parameter.

Наконец, в Main мы объявляем экземпляр Action с именем curried, который является результатом применения Curry к Foo с параметром 5. Это играет ту же роль, что и bind(fun_ptr(foo), 5) в вашем примере C++.

Наконец, мы вызываем вновь сформированный делегат curried через синтаксис curried(). Это похоже на someCallback() в вашем примере.

Причудливый термин для этого - карринг .

В качестве более интересного примера рассмотрим следующее:

class Program {
    static Func<TArg, TResult> Curry<TArg, TResult>(
        Func<TArg, TArg, TResult> func,
        TArg arg1
    ) {
        return arg => func(arg1, arg);
    }

    static int Add(int x, int y) {
        return x + y;
    }

    static void Main(string[] args) {
        Func<int, int> addFive = Curry<int, int>(Add, 5);
        Console.WriteLine(addFive(7));
    }
}

Здесь мы объявляем метод Curry, который принимает делегат (Func<TArg, TArg, TResult>, который принимает два параметра одного типа TArg и возвращает a значение некоторого другого типа TResult и параметр типа TArg и возвращает делегат, который принимает один параметр типа TArg и возвращает значение типа TResult (Func<TArg, TResult>).

Затем в качестве теста мы объявляем метод Add, который принимает два параметра типа int и возвращает параметр типа int (a Func<int, int, int>). Затем в Main мы создаем экземпляр нового делегата с именем addFive, который действует как метод, добавляющий пять к своему входному параметру. Таким образом
Console.WriteLine(addFive(7));

Отпечатки 12 на приставка.

Попробуйте следующее

class Example {
  static void foo(int i) {
    Console.WriteLine(i);
  }
  public static void Main() {
    Action someCallback = () => foo(5);
    someCallback();
  }
}

Или для чего-то еще более близкого к части счетчика C++

class Example {
  static void foo(int i) {
    Console.WriteLine(i);
  }
  static Action bind<T>(Action<T> action, T value) {
    return () => action(value);
  }
  public static void Main() {
    Action someCallback = bind(foo, 5);
    someCallback();
  }
}

Объяснение. Здесь происходит то, что я создаю нового делегата с помощью лямбда-выражения. Лямбда-это выражение, начинающееся с () =>. В этом случае он создает делегат, не принимающий никаких аргументов и не производящий никакого значения. Он совместим с типом Action.