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

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

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

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

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

Ответ 1

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

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. Action является делегатом, у которого нет параметров и возвращается void.

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

() => action(parameter)

говорит, что параметр void отображается на Action, оцененный в parameter.

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

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

Примером этого является currying.

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

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 и возвращает значение некоторого другого типа TResult и параметр типа TArg и возвращает делегат, который принимает единственный параметр типа TArg и возвращает значение типа TResult (Func<TArg, TResult>).

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

Console.WriteLine(addFive(7));

выводит 12 на консоль.

Ответ 2

Попробуйте выполнить

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

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

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.