В предыдущей статье
В предыдущей статье https://dzen.ru/a/aZlnznvuQ0wn-tsx?share_to=link мы разобрали, как можно подать функцию на вход другой функции:
Функция FilterTasks() фильтрует список. Она умеет применять любой фильтр, а логика самого фильтра вынесена в отдельную функцию, которую FilterTasks() получает на вход как condition. Вот как выглядит вызов FilterTasks:
В качестве второго аргумента ей передаётся функция CheckPriority, содержащая логику фильтрации:
Что такое лямбда-функции
Мы можем легко заменять логику фильтрации, заменяя CheckPriority на какую-нибудь другую функцию. Однако, чтобы написать новую функцию, потребуется написать довольно громоздкую конструкцию с возвращаемым типом, входными аргументами, return и прочим. Поэтому в C# существует упрощённый синтаксис для объявления функций - лямбда-функции. Они выглядят вот так:
Из символов "равно" и "больше" получается импровизированная стрелка. До стрелки идёт список входных параметров (через запятую). У нас это - один x. После стрелки - возвращаемое в ответ выражение. У нас это - результат сравнения "x.Priority >= 3". И ничего лишнего))
- Имя функции не указывается. В большинстве случаев мы используем функцию лишь в одном месте в коде, поэтому присваивать ей имя, чтобы можно было вызвать её из другого места, не требуется.
- Возвращаемый тип данных не указывается - C# может понять его по тому выражению, что стоит после стрелки.
- Типы входных аргументов тоже не указываются - C# обычно может понять их по контексту. Например, у нас лямбда написана как второй аргумент функции FilterTasks(). Она принимает на вход функцию, которая в свою очередь принимает на вход объект класса DeliveryTask. Так что можно сделать логический вывод, что x - это объект класса DeliveryTask. Но, если надо, то можно и указать типы.
- Фигурные скобки, обозначающие тело функции, а также return не указываются. Обычно в лямбдах тело функции состоит из одного выражения, поэтому фигурные скобки и return можно не писать. Но если вы хотите написать лямбду, у которой будет несколько команд - пожалуйста, ставьте фигурные скобки и return.
Примеры лямбда-функций
Лямбда на строке 66 складывает два целых числа. Тип данных этой лямбды Func<int, int, int> параметризован тремя типами int. Первые два из них - это типы входных переменных x и y, а последний - тип данных результата.
Лямбда на строках 67-68 проверяет, что задача имеет определённый адрес. Задача и адрес подаются на вход лямбде, в ответ выдаётся результат сравнения адресов ("равен ли адрес задачи адресу, поданному на вход?").
Вообще, тип данных Func<...> обозначает функцию, у которой все параметры типа, кроме последнего, - это типы входных аргументов, а последний - тип данных результата. Например, Func<DeliveryTask, string, bool> означает функцию, которая принимает на вход задачу доставки и строку и возвращает ответ логического типа данных.
Далее, лямбда на строках 69-70 записывает в задачу указанный адрес. Если на строке 68 было сравнение адресов (через "=="), то здесь у нас присваивание (через "="). Тип данных Action<...> обозначает функцию, которая не возвращает ничего в ответ. Все её параметры типа - это типы входных аргументов. Например, Action<DeliveryTask, string> обозначает функцию, получающую на вход 2 аргумента с типами данных DeliveryTask и string.
Основы Linq
На самом деле, для работы со списками и прочим в C# есть целая встроенная библиотека Linq.
В C# можно устроить цикл foreach не только по списку, но по любому типу данных, который является IEnumerable ("перечисление"). То есть, циклы можно делать по любому, что можно перечислять, как список. При этом параметр типа у IEnumerable<...> указывает тип данных элементов. То есть, IEnumerable<int> - это перечисление из элементов типа int, IEnumerable<bool> - перечисление из элементов типа bool и так далее.
Методы Linq можно применять к любому перечислению, но и результат методов - тоже всего лишь перечисление, а не список. Чтобы получить в ответ список, нужно использовать особый метод ToList().
У нас есть список data с задачами доставки. Отфильтруем и отсортируем его, используя Linq:
- Метод Where() фильтрует перечисление. Он проходится по перечислению и вызывает лямбду для каждого элемента. В результирующем перечислении остаются только те элементы, для которых лямбда вернула true. То есть, внутри метод Where() работает почти так же, как наш FilterTasks() из прошлой статьи https://dzen.ru/a/aZlnznvuQ0wn-tsx?share_to=link
- Далее у результирующего перечисления вызывается метод OrderByDescending(), который сортирует его по убыванию (для сортировки по возрастанию служит OrderBy()). Для каждого элемента перечисления вызывается лямбда, и все элементы сортируются по убыванию результата лямбды. В нашем случае - по убыванию приоритета. В результат выдаётся не просто IEnumerable<>, а IOrderedEnumerable<> - отсортированное перечисление. У него появляются методы ThenBy() и ThenByDescending() для дальнейшей сортировки по возрастанию и убыванию соответственно.
- Если мы хотим выдавать результаты на нескольких страницах (первая, вторая, третья...), то мы можем использовать методы Skip() и Take(), которые пропускают и берут соответственно указанное количество элементов. Задание: напишите соответствующий код для выдачи 3-ей страницы результатов поиска (нумерация с 1), если на каждой странице отображается по 5 элементов. Составьте тестовые данные и проверьте себя.
- Наконец, у результирующего перечисления вызывается метод ToList(), который превращает его в список. Мы можем распечатать список с помощью нашей PrintTaskList().
- В Linq есть множество других методов, посмотреть которые можно, в том числе, во всплывающей подсказке:
Разумеется, все функции Linq могут принимать на вход не только лямбды, но и полноценные функции. Важно лишь, чтобы они соответствовали требованиям к Func<...> / Action<...>.
Задания
Напишите свои аналоги Where(), Skip(), Take() для списков задач доставки (принимают на вход List<DeliveryTask> и возвращают другой такой список в ответ).
Далее
Пишем методы и конструкторы сами. Инициализаторы - https://dzen.ru/a/aZ14RYS-bHD9A3Gv?share_to=link
Оглавление - https://dzen.ru/a/aXisxwt_Mnz2qTjs?share_to=link