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);
  1. Enumerable.Range: получает диапазон целых чисел от 1(включительно) до n+1 (не включительно), то есть 1, 2, ... , n. Результатом является IEnumerable.
  2. Enumerable.Aggregate: объединяет их (то есть повторяет одну и ту же функцию для всех элементов в диапазоне) в соответствии с функцией/лямбда-выражением (m, n) => m * n. Чтобы уточнить, он вычисляет 1 * 2, берет на выходе 2, затем вычисляет output * (next input) = 2 * 3 и повторяет процесс, пока не охватит все элементы упорядоченной коллекции.

Enumerable.Aggregate был вашим первым опытом работы с лямбда-выражением. Он взял два входа (p и q) и просто перемножил их.

Обратите внимание, как очевидно, что 2 типа входных данных оба равны int, поэтому результатом является int, поскольку произведение 2 ints равно 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, чтобы использовать его методы расширения.