Skip to main content

C# vs Java. Делегаты

· 4 min read
Andrey Ganyushkin

C#

Делегат - это способ передачи кода между разными частями программы. Лямбда функции призваны выполнять аналогичные функции 😉.

lambda expression is a preferred way to write inline code

Тип делегата - тип ссылки на метод, сигнатура метода. Подобно function pointers в C++. Ссылка на метод - это всегда хорошо :), ее можно передать куда-нибудь, например как функцию обратного вызова.

Делегат не может хранить ссылку на любую функцию так как при его определении нужно предоставить сигнатуру функции которая ограничит подмножество функций (есть способ расширить это подмножество, см. ниже).

Определение типа и создание переменной

Создание делегата начинается с определение типа который является сигнатурой метода.

public delegate double MathOp(double x, double y);

// лубая функция имеющая сигнатуру: double (double x, double y)

в этом примере мы опретеляем тип который нашего делегат, он типизирован сигнатурой и может быть использован для создания переменных.

Тепер мы можем создать переменную

// внешняя функция Add
public double Add(double x, double y) => x + y

// Переменная, хранящая ссылку на метод
public MathOp externalOp = Add;

// Вот так мы можем использовать наш делегат и вызвать внешнюю функция,
// ссылка на которую была присвоена переменной externalOp имеющий тип телегата MathOp
var result = externalOp(2, 2);

Мультикаст делегаты

Делегат может хранить более одной ссылки. Добавить или удалить ссылку в контейнер можно с использованием "+=" или "-=".

invocation list - список ссылок на функции которые будут вызваны при активации.

Например так

public double Add(double x, double y) => x + y
public double Sub(double x, double y) => x - y

public MathOp externalOp = Add;
externalOp += Sub

var result = externalOp(1, 1);

Для корректной работы мультикаст делегаты не должны возвращать значение. Если в сигнатуре определено возвращаемое значение, как в примере выше, то тогда результатом выполнения бедет результат работы последнего метода.

Исключение, возникшее при вызове мультикаст делегата прерывает обработку invocation list и методы следующие за упавшим не будут вызваны. Распространенный метод борьбы с этим - использование Delegate.GetInvocationList и ручная активация делегатов.

Мультикаст делегаты будут вызываться последоватьельно в порядке добавления.

Обобщенный/Generic делегат

Ослабить ограничения типизации. Ну или расширить подмножество функций ссылки на которые может хранить делегат можно, как и в случае обячных функций, с помощью обобщений

Например

delegate T MathOp<T> (T x, T y);

// valid
int Add(int x, int y) => x + y;
MathOp<int> mathOp = Add;
int result = mathOp(2, 2);

// or, also valid
double Add(double x, double y) => x + y;
MathOp<double> mathOp = Add;
double result = mathOp(0.2, 0.2);

в этом примере мы опретеляем тип делегата которые параметризирован типом переменных.

Анонимные делегаты или оператор delegate

В C# есть возможность создавать анонимные делегаты, которые не требуют именованой переменной и могут быть сразу использованы/переданы например в событие.

Делается это так:

// without arguments
delegate { Console.WriteLine("Hello World"); };

// or with arguments
delegate (int a, int b) { return a + b; };

Зачем?

Тут есть вполне внятный ответ и документации

An anonymous method can be converted to types such as System.Action and System.Func<TResult> types used as arguments to many methods.

Пример

Func<int, int, int> constant = delegate (int _, int _) { return 42; };
Console.WriteLine(constant(3, 4)); // output: 42

NOTE: То же самое с lambda expressions будет выглядеть чуть проще

Func<int, int, int> constant = (a, b) => 42;
Console.WriteLine(constant(3, 4)); // output: 42

Статические делегаты

Пример из документации

Func<int, int, int> sum = static delegate (int a, int b) { return a + b; };
Console.WriteLine(sum(10, 4)); // output: 14

статические делегаты не могут захватывать локальных переменных.

Java

Такого механизма как deletages в джаву не завозили. Передать код для выполнения в другую часть программы можно с помощью лямбд или использовать интерфейс и анонимный класс. Описанные методы работают как Java так и в C# и будут рассмотрены отдельно.