Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Tip
Вы новичок в разработке программного обеспечения? Сначала начните с учебников для начинающих. Освойте основные навыки работы с типами и методами перед использованием лямбда-выражений.
Иногда требуется передать небольшую часть поведения, функцию непосредственно другому методу. Например, может потребоваться отфильтровать список, но условие фильтрации изменяется в зависимости от ситуации. Вместо написания отдельного именованного метода для каждого возможного условия вы передаете само условие в качестве аргумента.
Лямбда-выражения — это функция C#, которая делает это возможным. Лямбда-выражение — это компактная встроенная функция, которую вы пишете без указания имени. Оператор => со стрелкой используется для разделения списка параметров из текста:
x => x * 2
Чтение слева направо: x является входным параметром, => означает "ведет к", и x * 2 является телом. Он вычисляет возвращаемое значение. Если нет параметров или нескольких параметров, заключите их в скобки: () => 42 или (left, right) => left + right.
Делегаты поддерживают лямбда-выражения
Чтобы использовать лямбда-выражение, компилятор C# должен знать две вещи: типы параметров и возвращаемого типа. Это описание, типы параметров и возвращаемый тип, называется типом делегата.
Тип делегата — это тип, представляющий сигнатуру метода. Переменная типа делегата может содержать любой подходящий метод, например, лямбда-выражение или именованный метод, если типы его параметров и возвращаемого значения совпадают.
Вы объявляете тип делегата с ключевым словом delegate :
delegate int Transform(int value);
Это объявление говорит: "Transform является типом делегата для методов, которые принимают один int и возвращают объект int". Затем можно назначить лямбда-выражение или именованный метод переменной этого типа:
Transform doubler = x => x * 2; // assign a lambda expression
Transform squarer = Square; // assign a named method
Console.WriteLine(doubler(5)); // 10
Console.WriteLine(squarer(5)); // 25
static int Square(int value) => value * value;
И doubler, и squarer имеют значение типа Transform. Вы вызываете их точно так же, как обычные методы. Компилятор проверяет, совпадает ли то, что вы присваиваете, с объявленной сигнатурой.
Встроенные типы делегатов: FuncAction
Объявление пользовательского типа делегата для каждой ситуации может быть повторяющимся. .NET предоставляет два семейства универсальных типов делегатов, Func и Action, которые охватывают большинство сценариев, поэтому редко требуется использовать ключевое слово delegate самостоятельно.
Оба семейства доступны в версиях от нуля до шестнадцати входных параметров, поэтому они могут масштабироваться до любого количества входных данных. Ключевым различием между двумя семьями является:
-
System.Func<T,TResult> (и
Func<T1, T2, TResult>т. д.) представляет метод, возвращающий значение. Последний параметр типа всегда является возвращаемым типом; все предыдущие — это типы входных данных. -
System.Action<T> (и
Action<T1, T2>т. д.) представляет метод, возвращающий ничего (void). Все параметры типа — входные типы. System.Action без параметров типа представляет метод без входных данных и не возвращает возвращаемое значение.
Например, Func<int, int, int> описывает метод с двумя int входными данными и одним int результатом.
Action<string> описывает метод с одним string входным и не возвращаемым значением.
Func<int, int, int> add = (left, right) => left + right;
Action<string> report = message => Console.WriteLine($"Report: {message}");
int total = add(5, 9);
report($"5 + 9 = {total}");
Используйте описательные имена параметров в лямбда-файлах, чтобы читатели могли понять намерение без сканирования полного текста метода.
Передача лямбда-выражения методу
Когда метод объявляет параметр Func или Action, вызывающие передают лямбда-выражение, соответствующее типу делегата. Компилятор проверяет, соответствуют ли лямбда-типы параметров и тип возвращаемого значения объявленному типу делегата. Если они не соответствуют, код не компилируется.
int[] numbers = [1, 2, 3, 4, 5, 6];
int[] evenNumbers = Filter(numbers, value => value % 2 == 0).ToArray();
Console.WriteLine(string.Join(", ", evenNumbers));
Метод Filter объявляет Func<int, bool> параметр с именем predicate. Тип Func<int, bool> сообщает вызывающим сторонам ожидаемую структуру: один int вход, один bool результат. Вызывающий передает value => value % 2 == 0 в качестве этого аргумента. Этот шаблон используется во множестве API LINQ и .NET.
Делайте лямбда-выражения самодостаточными
Лямбда-выражение может ссылаться на переменные из окружающего кода. Захват означает, что лямбда содержит ссылку на переменную, объявленную за пределами собственного тела. Сочетание лямбда и переменных, которые она захватывает, называется замыканием.
Когда нет необходимости захватывать что-либо, добавьте модификатор static к лямбда-выражению. Статическая лямбда-функция может использовать только собственные параметры и значения, объявленные внутри тела функции. Не удается записать локальные переменные или состояние экземпляра из включающей области.
Func<int, bool> isEven = static value => value % 2 == 0;
Console.WriteLine(isEven(14));
Console.WriteLine(isEven(15));
Статические лямбды делают намерение понятным и предотвращают случайный захват.
Используйте параметры игнорирования, когда входные данные не актуальны
Иногда подпись делегата включает параметры, которые вам не нужны. Используйте параметр "отменить" _, чтобы явно указать на этот выбор.
Распространенные примеры включают обработчики событий, в которых вы не используете sender или EventArgs, обратные вызовы, в которых требуется только несколько входных данных, и перегрузки LINQ, которые предоставляют индекс, который не используется.
Action<int, int, string> statusUpdate = (_, _, message) => Console.WriteLine(message);
statusUpdate(200, 42, "Operation completed");
Исключения улучшают удобочитаемость, так как они показывают, какие параметры имеют значение.
События предоставляют необязательные уведомления
Событие — это механизм, который один объект (издатель) использует для уведомления других объектов (подписчиков) при возникновении чего-то. Издателю не нужно знать, кто слушает или сколько подписчиков есть. Подписчики решили принять участие.
События основаны на делегатах. Событие является полем делегата с дополнительными ограничениями, применяемыми ключевым словом event: внешний код может только подписываться (+=) или отменять подписку (-=) от события; только класс, объявляющий событие, может инициировать его (raise).
Соглашение .NET для типов делегатов событий — System.EventHandler<TEventArgs>, где T — это тип данных, включенных в уведомление. Его подпись всегда имеет два параметра: sender (объект, который вызвал событие) и данные события типа T.
MessagePublisher publisher = new();
publisher.MessagePublished += (_, message) => Console.WriteLine($"Received: {message}");
publisher.Publish("Records updated");
Пошаговое руководство по коду:
-
MessagePublisherevent EventHandler<string>? MessagePublishedобъявляет . Ключевое словоeventозначает, что подписчики могут только подписаться или отменить подписку — вызывать его напрямую нельзя. -
publisher.MessagePublished += (_, message) => ...подписывается с использованием лямбда-выражения. Обработчик игнорирует параметр_, поскольку он ему не нужен. -
publisher.Publish("Records updated")вызывает событие и запускает все подписанные обработчики.
Подписка является необязательной.
?.Invoke(...) Метод Publish означает, что событие возникает только при подключении по крайней мере одного подписчика. Издатель поднимает событие, не зная или заботясь о том, слушает ли кто-либо.