LINQ в С#
LINQ (Lязык INинтегрированные вопросыueries) — это системная библиотека на C#, которая позволяет вам аккуратно манипулировать упорядоченными коллекциями и предлагает читаемый код, особенно однострочный. При правильном использовании он может даже обойтись без этого за счет производительности. Он предлагает множество удобных методов, которые можно использовать в широком диапазоне приложений.
Все важные методы описаны в документации к Linq.Enumerable.
Эта статья, вероятно, ошеломит тех, кто плохо знаком с LINQ. Советую просмотреть здесь разнообразный список приложений, сохранить эту страницу и вернуться к ней, когда будет удобно.
Включение «использование
System.Linq;”
в начале файла позволяет вам получить доступ к этим методам расширения.
Основы
IEnumerables
IEnumerables — это просто списки объектов. Список дат — это IEnumerable. Список продуктов, которые нужно купить, представляет собой IEnumerable. Список контактов — это IEnumerable. Это просто коллекция, которую вы можете повторять.
Функции С#
Функция в C# — это любой статический или нестатический метод. Но что более важно, в LINQ вы увидите следующее:
Func<T1, T2, T3, T4, Tout>
Это функция, которая в данном случае принимает 4 входа (типа T1, ... , T4
) и возвращает тип Tout
. Вам редко потребуется явно указывать эти типы, так как C# выведет их самостоятельно. Если это не так, он должен выдать предупреждение или сообщение об ошибке, говорящее о том, что он не может этого сделать, и тогда вы можете явно указать эти типы.
Внутри статической функции может потребоваться, чтобы эти функции были статическими;
Лямбда-выражения
Лямбда-выражения — это просто укороченный синтаксис для функций, использование которых обычно носит временный характер. Их всегда можно заменить эквивалентной функцией.
Их обозначения просты:
([inputs]) => output
Например, чтобы включить лямбду, в которой 2 числа просто складываются друг с другом, мы пишем:
(x, y) => x + y
Если бы мы определяли функцию, мы бы написали так:
int Sum(int x, int y) { return x + y; }
Программа выводит типы входных и выходных данных на основе требуемых типов. Предположим, у нас есть что-то вроде этого:
int ApplyFunc(Func<int, int> func, int x, int y) { return func(x, y); }
Мы можем ввести метод Sum после его определения отдельно:
ApplyFunc(Sum, 2, 5);
Или мы можем вообще не определять метод Sum
, поэтому мы можем просто указать его в одну строку:
ApplyFunc((x, y) => x + y, 2, 5);
Это имеет точно такой же результат, как и раньше.
Наконец, лямбды с одним входом не требуют скобок. То есть это:
(x) => x * x;
можно записать так:
x => x * x;
Вы увидите это в действии в оставшейся части статьи.
Различные простые задачи
Документация уже охватывает различные примеры каждого метода на практике, так что в этом нет необходимости.
Факториалы
Факториал n
просто умножает первые n
положительных целых чисел. Факториал числа 4 равен 1 х 2 х 3 х 4 = 24.
public static int Factorial(int n)
=> Enumerable.Range(1, n+1).Aggregate((p, q) => p * q);
- Enumerable.Range: получает диапазон целых чисел от
1
(включительно) доn+1
(не включительно), то есть1, 2, ... , n
. Результатом является IEnumerable. - Enumerable.Aggregate: объединяет их (то есть повторяет одну и ту же функцию для всех элементов в диапазоне) в соответствии с функцией/лямбда-выражением
(m, n) => m * n
. Чтобы уточнить, он вычисляет1 * 2
, берет на выходе2
, затем вычисляетoutput * (next input) = 2 * 3
и повторяет процесс, пока не охватит все элементы упорядоченной коллекции.
Enumerable.Aggregate был вашим первым опытом работы с лямбда-выражением. Он взял два входа (p
и q
) и просто перемножил их.
Обратите внимание, как очевидно, что 2 типа входных данных оба равны int
, поэтому результатом является int
, поскольку произведение 2 int
s равно int
. В результате нам не нужно было писать .Aggregate<int, int, int>((p, q) => p * q)
, так как компилятор мог вывести его сам.
Сумма цен всех транзакций в счете-фактуре
Агрегирование сложения целых чисел или чисел с плавающей запятой функционально эквивалентно методам Enumerable.Sum
.
Предположим сначала, что у вас есть следующий предварительный код:
public class Invoice { public List<Transaction> transactions; } public class Transaction { public float price; }
Мы будем вычислять сумму цен двумя способами: с помощью Enumerable.Aggregate и Enumerable.Sum.
Использование агрегата:
invoice.transactions.Aggregate((x, y) => x.price + y.price);
Использование суммы:
invoice.transactions.Sum(x => x.price);
Оба они дают одинаковый результат. Однако здесь Sum
выглядят намного чище, чем Aggregate
.
Обратите внимание на отсутствие скобок в лямбда-выражении с одним входом в последнем блоке кода.
Список всех цен транзакций в счете-фактуре
Что, если мы хотим сделать с ценами другие вещи, а не суммировать их?
Метод Enumerable.Select позволяет взять все элементы списка и вывести другой список элементов на основе исходного списка. Элементы результирующего списка могут быть как одного типа, так и разных.
Теперь, чтобы получить список всех цен:
IEnumerable<int> prices = invoice.transactions.Select(t => t.price);
Один (не идеальный) способ суммировать все цены:
int sum = invoice.transactions.Select(t => t.price).Sum();
Однако, чтобы сложить числа, нет необходимости сначала собирать их через Enumerable.Select, так как вы можете использовать Enumerable.Sum напрямую:
int sum = invoice.transactions.Sum(t => t.price);
Минимум или максимум набора чисел
Скорее всего, вы уже видели методы Math.Min
и Math.Max
, но еще более мощными являются методы Enumerable.Min и Enumerable.Max.
Если у вас есть список чисел (целые числа, числа с плавающей запятой, удвоения и т. д.), вы можете просто вызвать Enumerable.Min или Enumerable.Max, чтобы получить их максимум или минимум:
List<int> list = new List<int> {2, 5, -12}; int min = list.Min(); int max = list.Max();
Но что, если у вас есть массив целых чисел? Для этого мы просто используем Enumerable.Cast‹int›, чтобы преобразовать его в IEnumerable
:
int[] arr = {2, 5, -12}; int min = arr.Cast<int>().Min(); int max = arr.Cast<int>().Max();
Замените все гласные символами подчеркивания
Во-первых, давайте напишем выражение, чтобы определить, является ли символ ch
гласным или нет. Следующий код достаточно прост:
"aeiouAEIOU".Contains(ch);
В C# строки наследуют IEnumerable‹char›, поэтому мы по-прежнему можем манипулировать ими с помощью LINQ.
string replaced = (string)str.Select(ch => { if ("aeiouAEIOU".Contains(ch) return '_'; else return ch; });
Я использовал здесь фигурные скобки для лямбды, так как мой код требовал более одного оператора (поскольку вместо 1 точки с запятой 2).
Приведенный выше код не должен быть сложным для чтения: если это гласная, верните _
по этому индексу, иначе верните сам символ. Нам нужно преобразовать его обратно в строку, поскольку string
наследует IEnumerable<char>
, а не наоборот.
Поскольку это манипуляция со строками, это можно сделать с помощью небольшого регулярного выражения. Но я объясняю здесь LINQ, а не регулярное выражение.
Группировка списка игроков в игре по их классу
Вы слышали о занятиях по ролевым играм (если играли в них). Если нет, то в большинстве игр есть как минимум 4 типа: воины, лучники, маги, целители.
Предположим, ваш предварительный код выглядел примерно так:
public enum Class { Warrior, Archer, Mage, Healer } public class Player { ... public Class PlayerClass; ... }
Мы не хотим просто использовать Enumerable.Select, так как мы хотим фактически сгруппировать их вместе. Это удобно, если я хочу просто посмотреть, кто сейчас лучник, а потом, может быть, посмотреть, кто маги.
Чтобы сгруппировать, мы просто используем Enumerable.GroupBy:
playerList.GroupBy(p => p.PlayerClass);
Это возвращает IEnumerable‹IGrouping‹Class››. Это новый список группировок. У каждой из этих групп есть нечто, называемое Ключ. Ключи здесь будут такими, какими вы их ожидаете: классы игроков в списке игроков для игры.
Но как узнать, кто входит в группу? Простой, IGrouping‹T› также является IEnumerable, поэтому мы также можем перебирать его элементы. А сами IGrouping содержат игроков с данным классом в качестве ключа IGrouping.
Чтобы получить всех магов в виде списка, мы можем сделать:
playerList.GroupBy(p => p.PlayerClass) // Group by classes .Single(g => g.Key == Class.Mage) // Gets mage grouping
Enumerable.Single просто получает единственный элемент в группе, удовлетворяющий условию. Если их нет или их больше одного, выдается исключение.
Однако есть более простой способ получить всех магов в списке игроков:
playerList.Where(p => p.PlayerClass == Class.Mage);
Это просто извлекает все элементы в IEnumerable, которые имеют PlayerClass of Mage.
В заключении
Если вы запутались, просто проверьте документацию для Enumerable и щелкните раскрывающийся список Методы с левой стороны, чтобы увидеть его список методов. Советую выполнить несколько задач на Code Wars и почитать чужие решения, чтобы понаблюдать за LINQ на практике.
LINQ — очень мощный инструмент. Я рассказал столько, сколько смог за один день, и, надеюсь, вы узнаете больше о преимуществах LINQ. Помните, что вам нужно using System.Linq
, чтобы использовать его методы расширения.