Используем LINQ для быстрой выборки из списков

volody00

Client
Регистрация
06.09.2016
Сообщения
896
Благодарностей
915
Баллы
93
Долгое время я откладывал изучение LINQ, полагая, что это довольно трудно. На деле же основы оказались очень простыми, при этом польза довольно ощутима. Изучив эту статью, вы сможете буквально в пару строк сможете делать выборки из списков, сортировать их, удалять дубли и т.д. Всё это мы и рассмотрим в этой статье.

Введение

Я не умею рассказывать "по-научному", так что давайте сразу посмотрим пример простого выражения и разберем его

C#:
List<string> fruits = new List<string>() {"mango", "banana", "apple"};
List<string> result = fruits.Where(x=>x.Contains("n")).ToList();
foreach(var s in result) project.SendInfoToLog(s.ToString()); //mango, banana
Первое, что у нас тут есть, это список с фруктами (fruits). Вы можете использовать и зенковские списки, покажу чуть ниже, чтобы пока не путаться.

Результат будет положен в другой список с именем result.

Для выборки элементов из списка fruits мы используем метод Where, где в качестве параметра идет делегат. Но мы его как бы "составляем на ходу" и просто передаем лямбда выражение. Where у нас проверяет какое-то условие, и если оно истинно (возвращает true), то кладет элемент в новый список. Если нет (false), то пропускает этот элемент.

В "x" у нас кладется текущий элемент списка. Т.е. сначала там окажется "mango", на следующем круге "banana", затем "apple". Это как в цикле foreach, если вы с ним знакомы.

Каждый круг мы проверяем условие, которое идет после лямбды =>. В данном случае это выражение x.Contains("п"). Оно означает "содержит ли 'x' букву 'n'". Если да, то выражение вернет true, и мы положим текущий элемент в result. mango содержит n? Да, значит кладем в result. banana содержит n? Тоже кладем. apple содержит? Нет, значит его не берем.

ToList() преобразует IEnumerable (который возвращается по умолчанию) в List (т.е. в список). Лучше всегда преобразовывать либо в список, либо в массив (ToArray), т.к. с IEnumerable действует отложенное выполнение, на котором я не буду заостряться.

В результате у нас получается новый список с двумя элементами, удовлетворяющие заданному нами условию (x.Contains("n")).

В конце с помощью foreach выводим всё в лог.

==

Помимо Contains вы можете использовать абсолютно любое выражение, возвращающее true или false. Приведу несколько примеров.
C#:
List<string> fruits = new List<string>() {"mango", "banana", "apple"};
List<string> result = fruits.Where(x=>x.StartsWith("m")).ToList(); //начинается ли на букву m
foreach(var s in result) project.SendInfoToLog(s.ToString()); //mango

List<string> fruits = new List<string>() {"mango", "banana", "apple"};
List<string> result = fruits.Where(x=>x.Length == 5).ToList(); //длина слова равна 5
foreach(var s in result) project.SendInfoToLog(s.ToString()); //mango, apple
Вы можете создавать и более сложные условия. Для этого используем фигурные скобки и пишем там, что душе угодно. Главное в конце вернуть булевое значение (для метода Where, для других возвращаем то, что ему нужно)
C#:
List<string> fruits = new List<string>() {"mango", "banana", "apple"};

List<string> result = fruits.Where(x=>{

            if(x.Length == 5 && x.StartsWith("m"))

            {

                        return true; //возвращаем, если длина == 5 и начинается на букву ,

            }

            return false; //во всех остальных случаях не возвращаем

}).ToList();



foreach(var s in result) project.SendInfoToLog(s.ToString()); //mango
Всё то же самое можно делать и для зенковских списков, привязанных к файлам. Для обращения к зенковскому списку мы пишем project.List["название списка"]. Создайте список с именем "Список 1", привяжите к файлу, а в него забейте строки

mango
banana
apple

и запустите код
C#:
List<string> fruits = project.Lists["Список 1"].ToList(); //копируем данные из зенно списка в обычный
List<string> result = fruits.Where(x=>{

if(x.Length == 5 && x.StartsWith("m"))
{
    return true;
}
return false;
}).ToList();

//очищаем старые данные и кладем новые. lock для блокировки в многопотоке
lock(SyncObjects.ListSyncer)
{
    project.Lists["Список 1"].Clear();
    project.Lists["Список 1"].AddRange(result);
}
Т.к. почти все методы LINQ возвращают IEnumerable, мы можем запускать их по цепочке, выполняя сразу несколько операций над списком. В примере ниже мы сначала отберем слова, в которых содержится буква "n", затем переведем их в верхний регистр (с помощью метода Select), а затем отсортируем (с помощью метода OrderBy)
C#:
List<string> fruits = new List<string>() {"mango", "banana", "apple"};
List<string> result = fruits.Where(x=>x.Contains("n"))
    .Select(x=>x.ToUpper())
    .OrderBy(x=>x)
    .ToList();

foreach(var s in result) project.SendInfoToLog(s.ToString()); //BANANA, MANGO
Помимо списков можно использовать массивы и другие коллекции, но некоторые из этих коллекций не имеют доступа к методам LINQ, но у них есть метод Cast. Если он есть, то используя этот метод можно будет пользоваться методами LINQ. Какие именно это коллекции - я не знаю (наверное, которые не реализуют интерфейс IEnumerable). Одна из таких - это MatcCollection (для работы с регулярками)
C#:
string str = "вася пошёл гулять";
string regex = @"вася"; //регулярка

List<string> result = Regex.Matches(str,regex) //метод Matches возвращает MatchCollection
    .Cast<Match>()  //используя Cast получаем доступ к методам LINQ
    .Select(x=>x.ToString()) //select используется для получения текущего значения и его видоизменения
    .ToList(); //преобразуем в список
Мы можем использовать подзапросы. Пример сортировки по фамилии
C#:
List<string> names = new List<string> { "Василий Пупкин", "Аркадий Соловьёв", "Генадий Петров"};

//используем подзапрос, сортируем по фамилии
var res = names.OrderBy(x=>x.Split().Last());
foreach(var s in res) project.SendInfoToLog(s);
Ну и ещё упомяну, что в LINQ есть иной синтаксис, который считается более удобным. Я с этим не согласен плюс там есть свои нюансы, так что я вам его покажу, но рассматривать не буду. В этом случае мы начинаем с from и заканчиваем либо select, либо group.
C#:
List<string> fruits = new List<string>() {"mango", "banana", "apple"};

var result = from s in fruits
    where s.Length == 5
    select s;

foreach(var s in result) project.SendInfoToLog(s); //mango, apple
А теперь давайте рассмотрим некоторые из методов LINQ.

Методы LINQ

[1 перегрузка]Aggregate<TSource> (this System.Collections.Generic.IEnumerable<TSource> source, Func<TSource,TSource,TSource> func) – функция в Aggregate принимает два параметра, где первый это текущий результат, а второй текущий элемент. Например, в примере ниже сначала в "x" кладётся apple, а в "y" passionfruit. Получается, что у нас вернулось "apple. passionfruit". В следующей итерации в "x" как раз и будет эта строка ("apple. passionfruit"), а в "y" кладётся следующий элемент, т.е. banana. У нас получается "apple. passionfruit. banana" и на следующем этапе уже это будет в "x", ну а в "y", соответственно, mango. Ну и т.д.

C#:
List<string> fruits =
    new List<string> { "apple", "passionfruit", "banana", "mango",
                    "orange", "blueberry", "grape", "strawberry" };

string query = fruits.Aggregate((x,y)=> {
    return x + ". " + y;
});
   
return query; //apple. passionfruit. banana. mango. orange. blueberry. grape. strawberry
Другой пример
C#:
string[] fruits = { "apple", "mango", "orange", "passionfruit", "grape" };

string result1 = fruits.Aggregate((next, longest) => next += $". {longest}"); //apple. mango. orange. passionfruit. grape
string result2 = fruits.Aggregate((next, longest) => next.Length > longest.Length ? next : longest); //passionfruit. мы получили самое длинное слово

List<int> numbers = new List<int>(){ 1, 2, 3, 4, 5 };
int sum = numbers.Aggregate((x, y)=> x + y); //15. получили сумму всех чисел
int sumRandom = numbers.Aggregate((x, y) => {
    int summa=0;
    if(y > 2)
    {
        summa = x+y;
    }
    return summa;
});
return sumRandom;//12. тут я показал как можно использовать более сложную логику. просто открываем {} и пишем что душе угодно
[2 перегрузка] public static TResult Aggregate<TSource,TAccumulate,TResult> (this System.Collections.Generic.IEnumerable<TSource> source, TAccumulate seed, Func<TAccumulate,TSource,TAccumulate> func, Func<TAccumulate,TResult> resultSelector)
C#:
string[] fruits = { "apple", "mango", "orange", "passionfruit", "grape" };

//параметры: изначальное значение, с которым сравниваем, делегат, финальное действие с результатом (делегат)
string longestName =
    fruits.Aggregate("banana",
                    (longest, next) =>
                        next.Length > longest.Length ? next : longest,
                        fruit => fruit.ToUpper()); //PASSIONFRUIT

string longestName2 =
    fruits.Aggregate("banana2222222222222222222222222",
                    (longest, next) =>
                        next.Length > longest.Length ? next : longest,
                        fruit => fruit.ToUpper()); //BANANA2222222222222222222222222
return longestName2;
[3 перегрузка] public static TAccumulate Aggregate<TSource,TAccumulate> (this System.Collections.Generic.IEnumerable<TSource> source, TAccumulate seed, Func<TAccumulate,TSource,TAccumulate> func) – аналогична второй, только тут в конце не производится финальное действие с результатом
C#:
string[] fruits = { "apple", "mango", "orange", "passionfruit", "grape" };

//параметры: изначальное значение, с которым сравниваем, делегат, финальное действие с результатом (делегат)
string longestName =
    fruits.Aggregate("banana",
                    (longest, next) =>
                        next.Length > longest.Length ? next : longest
                        );

return longestName; //passionfruit
public static bool All<TSource> (this System.Collections.Generic.IEnumerable<TSource> source, Func<TSource,bool> predicate) – проверяет, соответствуют ли все элементы указанному условию. Вернет true, если все элементы проходят условие.
C#:
string[] fruits = { "apple", "mango", "orange", "passionfruit", "grape" };
bool result = fruits.All(x=> x.Length > 4);
return result; //True, т.к. все элементы имеют  длину больше 4 символов
public static bool Any<TSource> (this System.Collections.Generic.IEnumerable<TSource> source, Func<TSource,bool> predicate) – проверяет, соответствует ли хотя бы один элемент коллекции указанному условию.
C#:
string[] fruits = { "apple", "mango", "orange", "passionfruit", "grape" };
bool result = fruits.Any(x=> x.Length > 10);
return result; //True, т.к. passionfruit больше 10 символов
public static System.Collections.Generic.IEnumerable<TSource> Append<TSource> (this System.Collections.Generic.IEnumerable<TSource> source, TSource element) – добавляет элемент в конец и возвращает новую коллекцию
C#:
string[] fruits = { "apple", "mango", "orange", "passionfruit", "grape" };
string[] fruitsAddFruit = fruits.Append("banana").ToArray();
foreach(var s in fruitsAddFruit) project.SendInfoToLog(s);
public static System.Collections.Generic.IEnumerable<TSource> Prepend<TSource> (this System.Collections.Generic.IEnumerable<TSource> source, TSource element) – аналогичен Append, но добавляет в начало
C#:
string[] fruits = { "apple", "mango", "orange", "passionfruit", "grape" };
string[] fruitsAddFruit = fruits.Prepend("banana").ToArray();
return fruitsAddFruit[0]; //banana
public static double Average<TSource>(…) – считает среднее значение. можно использовать для всех числовых типов + для других тоже, если указать как будем считать среднее значение
C#:
List<int> numbers = new List<int>
{
    1, 2, 3, 4, 5
};
double average = numbers.Average(); //3 т.к. это (1+2+3+4+5) / 5 == 3
project.SendInfoToLog(average.ToString());

string[] fruits = { "apple", "banana", "mango", "orange", "passionfruit", "grape" };
average = fruits.Average(s => s.Length); //6.5 средняя длина фраз
project.SendInfoToLog(average.ToString());
public static System.Collections.Generic.IEnumerable<TResult> Cast<TResult> (this System.Collections.IEnumerable source) – некоторым коллекциям недоступны методы LINQ (OrderBy, Select и т.д.). Каким именно - я до конца не разобрался. Но если у этих коллекций есть метод Cast, тогда, использовав его, мы сможем вызывать наши методы
C#:
MatchCollection kol = Regex.Matches(@"Васе Пупкину 12 лет, а Пете 9 лет", @"\d+");
List<Match> result = kol.Cast<Match>().OrderBy(x => Convert.ToInt32(x.Value)).ToList();
foreach(var s in result) project.SendInfoToLog(s.ToString()); //9, 12

System.Collections.ArrayList fruits = new System.Collections.ArrayList();
fruits.Add("mango");
fruits.Add("apple");
fruits.Add("lemon");

IEnumerable<string> query = fruits.Cast<string>().OrderBy(fruit => fruit).Select(fruit => fruit);
foreach(var s in query) project.SendInfoToLog(s.ToString()); //apple, lemon, mango
public static System.Collections.Generic.IEnumerable<TSource> Concat<TSource> (this System.Collections.Generic.IEnumerable<TSource> first, System.Collections.Generic.IEnumerable<TSource> second) – соединяет две коллекции
C#:
string[] array = {"Первый", "Второй", "Третий", "Четвертый", "Пятый"};
string[] array2 = {"Первый", "Второй", "Третий", "Четвертый", "Пятый"};
IEnumerable<string> list = array.Concat(array2);
foreach(var s in list) project.SendInfoToLog(s);
public static bool Contains<TSource> (this System.Collections.Generic.IEnumerable<TSource> source, TSource value) – содержится ли элемент в коллекции
C#:
string[] array = {"Первый", "Второй", "Третий", "Четвертый", "Пятый"};
bool result = array.Contains("Второй"); //True
[1 перегрузка] public static int Count<TSource> (this System.Collections.Generic.IEnumerable<TSource> source) – вернёт количество элементов,
[2 перегрузка] public static int Count<TSource> (this System.Collections.Generic.IEnumerable<TSource> source, Func<TSource,bool> predicate) – можно задать доп. условие.
C#:
string[] array = {"Первый", "Второй", "Третий", "Четвертый", "Пятый"};
int result = array.Count(); //5
int result2 = array.Count(x => x.Length > 6); //1. Возвращаем количество элементов, чья длина больше 6
return result2;
public static long LongCount<TSource> (…) – аналогичен Count(), только возвращает не int, а long.

[1 перегрузка] public static System.Collections.Generic.IEnumerable<TSource?> DefaultIfEmpty<TSource> (this System.Collections.Generic.IEnumerable<TSource> source) – если коллекция пуста, то добавляется значение по умолчанию.

[2 перегрузка] public static System.Collections.Generic.IEnumerable<TSource> DefaultIfEmpty<TSource> (this System.Collections.Generic.IEnumerable<TSource> source, TSource defaultValue) – если коллекция пуста, то добавляется указанное нами значение
C#:
string[] array = {};
List<string> resultList1 = array.DefaultIfEmpty("").ToList();
List<string> resultList2 = array.DefaultIfEmpty("gg").ToList();
string result1 = resultList1[0]; //""
string result2 = resultList2[0]; //gg
project.SendInfoToLog($"{result1}\n{result2}");
[1 перегрузка] public static System.Collections.Generic.IEnumerable<TSource> Distinct<TSource> (this System.Collections.Generic.IEnumerable<TSource> source) – удаляет дубли

[2 перегрузка] public static System.Collections.Generic.IEnumerable<TSource> Distinct<TSource> (this System.Collections.Generic.IEnumerable<TSource> source, System.Collections.Generic.IEqualityComparer<TSource>? comparer) - для удаления дублей у своих объектов
C#:
string[] array = {"Первый", "Второй", "Пятый", "Третий", "Третий", "Четвертый", "Пятый", "Пятый"};
string[] newArray = array.Distinct().ToArray();
foreach(var s in newArray) project.SendInfoToLog(s);
public static TSource ElementAt<TSource> (this System.Collections.Generic.IEnumerable<TSource> source, int index) – получение отдельного элемента из коллекции
C#:
string[] array = {"Первый", "Второй", "Третий", "Четвертый", "Пятый"};
string el = array.ElementAt(1); //Второй
public static TSource? ElementAtOrDefault<TSource> (this System.Collections.Generic.IEnumerable<TSource> source, int index) – аналогичен ElementAt(), только если будет обращение по индексу вне диапазона, тогда вместо ошибки получим значение по умолчанию.
C#:
string[] array = {"Первый", "Второй", "Третий", "Четвертый", "Пятый"};
string el = array.ElementAtOrDefault(11); //null
[1 перегрузка] public static System.Collections.Generic.IEnumerable<TSource> Except<TSource> (this System.Collections.Generic.IEnumerable<TSource> first, System.Collections.Generic.IEnumerable<TSource> second) – метод оставит только те значения, что уникальны для первой коллекции (т.е. их нет во второй). Также если в первой коллекции есть дубли, они будут убраны. Вторая перегрузка нужна для своих объектов.
C#:
double[] numbers1 = { 2.0, 2.0, 2.1, 2.2, 2.3, 2.3, 2.4, 2.5 };
double[] numbers2 = { 2.2 };

IEnumerable<double> onlyInFirstSet = numbers1.Except(numbers2);

foreach (double number in onlyInFirstSet)
    project.SendInfoToLog(number.ToString()); //2.0, 2.1, 2.3, 2.4, 2.5
[1 перегрузка] public static System.Collections.Generic.IEnumerable<TSource> Intersect<TSource>(…) – аналогичен Except, только ищет уже пересечения, т.е. те элементы, что встречаются в обоих коллекциях. Вторая перегрузка нужна для своих объектов.
C#:
double[] numbers1 = { 2.0, 2.0, 2.1, 2.2, 2.3, 2.3, 2.4, 2.5 };
double[] numbers2 = { 2.0, 2.0, 2.9, 2.1, 2.8 };
IEnumerable<double> result = numbers1.Intersect(numbers2);
foreach(var s in result) project.SendInfoToLog(s.ToString()); //2.0, 2.1
[1 перегрузка] public static TSource First<TSource> (this System.Collections.Generic.IEnumerable<TSource> source) – возвращает первый элемент коллекции.

[2 перегрузка] public static TSource First<TSource> (this System.Collections.Generic.IEnumerable<TSource> source, Func<TSource,bool> predicate) – можно задать условие
C#:
double[] numbers1 = { 2.0, 2.0, 2.1, 2.2, 2.3, 2.3, 2.4, 2.5 };

double result = numbers1.First(); //2.0
double result2 = numbers1.First(x=> x > 2.3); //2.4
public static TSource FirstOrDefault<TSource>(…) – аналогичен First, но если коллекция пустая, то вернёт значение по умолчанию.
C#:
double[] numbers1 = {};

double result = numbers1.FirstOrDefault(); //0
double result2 = numbers1.FirstOrDefault(x=> x> 2); //0
public static TSource Last<TSource> (…)

public static TSource? LastOrDefault<TSource> (…) – аналогичен First() и FirstOrDefault(), только возвращает последнее совпадение.
C#:
double[] numbers1 = { 2.0, 2.0, 2.1, 2.2, 2.3, 2.3, 2.4, 2.5 };

double result = numbers1.Last(); //2.5
double result2 = numbers1.Last(x=> x < 2.3); //2.2
public static … Max (...) – возвращает наибольший элемент в коллекции. Можно задать доп условие (но меняется поведение для string, см пример). Строки он смотрит по алфавиту, т.е. при исп-и Max вернётся то значение, которое "дальше" всего по алфавиту
C#:
string[] array = {"Первый", "Второй", "Третий", "Четвертый", "Пятый"};
string strResult = array.Max(); //Четвертый

int[] iArray = {55, 243, 1, 15};
int iResult = iArray.Max(); //243

int strResultPlus = array.Max(x=> x.Length + 5); //14. string он не возвращает вообще, поэтому считается длина слова + 5
public static … Min (...) – аналогичен Max(), только возвращает минимальное значение
C#:
string[] array = {"Первый", "Второй", "Третий", "Четвертый", "Пятый"};
string strResult = array.Min(); //Второй

int[] iArray = {55, 243, 1, 15};
int iResult = iArray.Min(); //1

int strResultPlus = array.Min(x=> x.Length); //5. т.к. я ему сказал брать длину слова, то он уже сравнивает по ней
public static System.Collections.Generic.IEnumerable<TResult> OfType<TResult> (this System.Collections.IEnumerable source) – отбирает элементы указанного типа
C#:
System.Collections.ArrayList fruits = new System.Collections.ArrayList()
{
    "Mango",
    "Orange",
    null,
    "Apple",
    3.0,
    "Banana"
};

// Apply OfType() to the ArrayList.
IEnumerable<string> query1 = fruits.OfType<string>();

foreach (string fruit in query1)
{
    project.SendInfoToLog(fruit); //Mango, Orange, Apple, Banana
}
public static System.Linq.IOrderedEnumerable<TSource> OrderBy<TSource,TKey> (this System.Collections.Generic.IEnumerable<TSource> source, Func<TSource,TKey> keySelector) (this System.Collections.Generic.IEnumerable<T> source) – сортировка коллекции
C#:
string[] fruits = { "apple", "banana", "mango", "orange", "passionfruit", "grape" };

string[] newFruits = fruits.OrderBy(x=>x).ToArray();
foreach(var s in newFruits) project.SendInfoToLog(s); //apple, banana, grape, ...
public static System.Linq.IOrderedEnumerable<TSource> OrderByDescending<TSource,TKey> (…) – аналогичен OrderBy, только в обратном порядке
C#:
string[] fruits = { "apple", "banana", "mango", "orange", "passionfruit", "grape" };

string[] newFruits = fruits.OrderByDescending(x=>x).ToArray();
foreach(var s in newFruits) project.SendInfoToLog(s); // passionfruit, orange, mango, ...
public static System.Collections.Generic.IEnumerable<int> Range (int start, int count) – генерация чисел подряд от какого-то. например, от десяти/пятнадцать штук (будет от 10 до 25)
C#:
IEnumerable<int> squares = Enumerable.Range(5, 10); //параметры: откуда начинаем, сколько.

foreach (int num in squares)
{
    project.SendInfoToLog(num.ToString()); //результат: 5,6,7,8,9,10,11,12,13,14
}
public static System.Collections.Generic.IEnumerable<TResult> Repeat<TResult> (TResult element, int count) – "размножить" элемент
C#:
IEnumerable<string> strings =
    Enumerable.Repeat("I like programming.", 4);

foreach (String str in strings)
{
    project.SendInfoToLog(str); //I like programming. , I like programming. , I like programming. , I like programming.
}
public static System.Collections.Generic.IEnumerable<TSource> Reverse<TSource> (this System.Collections.Generic.IEnumerable<TSource> source) – "перевернуть" коллекцию
C#:
string[] fruits = { "apple", "banana", "mango", "orange", "passionfruit", "grape" };
string[] revers = fruits.Reverse().ToArray();
foreach(var s in revers) project.SendInfoToLog(s); //grape, passionfruit, orange, ...
[1 перегрузка] public static System.Collections.Generic.IEnumerable<TResult> Select<TSource,TResult> (this System.Collections.Generic.IEnumerable<TSource> source, Func<TSource,TResult> selector) – работает с элементами коллекциями (можно видоизменять)
C#:
string[] array = {"Первый", "Второй", "Третий", "Четвертый", "Пятый"};

int i=1;
var list2 = array.Select(x => i++.ToString() + ". " + x);
foreach(var s in list2) project.SendInfoToLog(s);
/*
1. Первый
2. Второй
3. Третий
4. Четвертый
5. Пятый
*/
[2 перегрузка] public static System.Collections.Generic.IEnumerable<TResult> Select<TSource,TResult> (this System.Collections.Generic.IEnumerable<TSource> source, Func<TSource,int,TResult> selector) – то же самое, только появляется индекс у элементов (с нуля), которым можно пользоваться.
C#:
string[] array = {"Первый", "Второй", "Третий", "Четвертый", "Пятый"};

var list2 = array.Select((x, index) => index.ToString() + ". " + x);
foreach(var s in list2) project.SendInfoToLog(s);
/*
0. Первый
1. Второй
2. Третий
3. Четвертый
4. Пятый
*/
public static System.Collections.Generic.IEnumerable<TResult> SelectMany<TSource,TResult> (…) - метод SelectMany проецирует каждый элемент последовательности в коллекцию и объединяет результаты в одну последовательность. Он используется для работы с коллекциями коллекций, где требуется "развернуть" вложенные коллекции в одну последовательность. Есть перегрузка с индексом (как у Select) и ещё парочку (их я не рассматривал)
C#:
List<List<int>> numbers = new List<List<int>>
{
    new List<int>
    {
        1, 2, 3
    },
            new List<int>
    {
        4, 5, 6
    },
            new List<int>
    {
        7, 8, 9
    }
};

var allNumbers = numbers.SelectMany(list => list);

foreach (var number in allNumbers)
{
    project.SendInfoToLog(number.ToString()); //1,2,3,4,5,6,7,8,9
}
Вот демонстрация отличий Select и SelectMany
C#:
List<string> sentences = new List<string>
{
    "Hello World",
            "LINQ is awesome",
            "C# is great"
};

// Select
IEnumerable<string[]> words1 = sentences.Select(sentence => sentence.Split(' '));
project.SendInfoToLog(words1.ElementAt(0).ElementAt(0)); //Hello
project.SendInfoToLog(words1.ElementAt(0).ElementAt(1)); //World
project.SendInfoToLog(words1.ElementAt(1).ElementAt(0)); //LINQ


project.SendInfoToLog("============================");
// SelectMany
IEnumerable<string> words2 = sentences.SelectMany(sentence => sentence.Split(' '));

foreach (var word in words2)
{
    project.SendInfoToLog(word);
}
public static bool SequenceEqual<TSource> (this System.Collections.Generic.IEnumerable<TSource> first, System.Collections.Generic.IEnumerable<TSource> second) – сравнивает две коллекции. Если у них одинаковые элементы в одной и той же последовательности, то вернёт true. 2-я перегрузка для своих объектов.
C#:
string[] fruits1 = {"banana", "apple", "orange"};
string[] fruits2 = {"banana", "apple", "orange"};
string[] fruits3 = {"apple", "banana", "orange"};

bool result1 = fruits1.SequenceEqual(fruits2); //true
bool result2 = fruits1.SequenceEqual(fruits3); //false
project.SendInfoToLog($"result 1: {result1.ToString()}, result 2: {result2.ToString()}");
public static TSource Single<TSource> (this System.Collections.Generic.IEnumerable<TSource> source, Func<TSource,bool> predicate) - возвращает единственный элемент последовательности, удовлетворяющий указанному условию, и выдает исключение, если существует более одного такого элемента или их не существует ни одного.
C#:
string[] fruits = {"banana", "apple", "orange"};

string result1 = fruits.Single(x=> x.Length == 5); //apple
try{
    string result2 = fruits.Single(x=>x.Length == 6); //перебросит в catch
   
}
catch(Exception e){
    project.SendInfoToLog("ошибка в методе Single, т.к. более одного элемента удовлетворяют условию. описание ошибки - " + e.Message);
}
public static TSource? SingleOrDefault<TSource> (this System.Collections.Generic.IEnumerable<TSource> source, Func<TSource,bool> predicate) – аналогичен Single(), но если ничего не найдено, вернёт значение по умолчанию
C#:
string[] fruits = {"banana", "apple", "orange"};

string result1 = fruits.SingleOrDefault(x=> x.Length == 15); //null
public static System.Collections.Generic.IEnumerable<TSource> Skip<TSource> (this System.Collections.Generic.IEnumerable<TSource> source, int count) – позволяет пропустить указанное количество элементов из коллекции и вернуть остальное
C#:
string[] fruits = {"banana", "apple", "orange", "1", "2", "3"};

string[] result = fruits.Skip(3).ToArray(); //1,2,3
foreach(var s in result) project.SendInfoToLog(s);
public static System.Collections.Generic.IEnumerable<TSource> SkipWhile<TSource> (…) – пока элементы удовлетворяют условию, они пропускаются. Как только условие становится ложным, происходит возврат всех оставшихся элементов. Во второй перегрузке появляется возможность использовать index, который с каждым элементом возрастает на один.
C#:
string[] fruits = {"banana", "apple", "orange", "1", "2", "3", "end"};

string[] result = fruits.SkipWhile(x=> Regex.Match(x, @"[a-z]").Success).ToArray(); //"1", "2", "3", "end"
foreach(var s in result) project.SendInfoToLog(s);

int[] amounts = { 5000, 2500, 9000, 8000,
                    6500, 4000, 1500, 5500 };

IEnumerable<int> query =
    amounts.SkipWhile((amount, index) => amount > index * 1000); //4000, 1500, 5500
public static … Sum (…) – ищет сумму чисел. Доступен для всех числовых типов. Для своих объектов тоже можно, указав как именно считать сумму.
C#:
string[] fruits = {"banana", "apple", "orange", "1", "2", "3", "end"};

int sum = fruits.Sum(x=> x.Length); // получаем сумму длин слов
return sum; //23.
public static System.Collections.Generic.IEnumerable<TSource> Take<TSource> (this System.Collections.Generic.IEnumerable<TSource> source, int count) – возвращает указанное количество элементов из коллекции
C#:
string[] fruits = {"banana", "apple", "orange", "1", "2", "3", "end"};

var sum = fruits.Take(2);
foreach(var s in sum) project.SendInfoToLog(s); //banana, apple
public static System.Collections.Generic.IEnumerable<TSource> TakeWhile<TSource> (this System.Collections.Generic.IEnumerable<TSource> source, Func<TSource,int,bool> predicate) - возвращает элементы до тех пор, пока они соответствуют условию
C#:
string[] fruits = {"banana", "apple", "orange", "1", "2", "3", "ends"};

var sum = fruits.TakeWhile(x=> x.Length > 3);
foreach(var s in sum) project.SendInfoToLog(s); //banana, apple, orange
public static System.Linq.IOrderedEnumerable<TSource> ThenBy<TSource,TKey> (this System.Linq.IOrderedEnumerable<TSource> source, Func<TSource,TKey> keySelector) - Выполняет последующее упорядочивание элементов в последовательности в порядке возрастания. Вторая перегрузка для своих объектов.

В примере ниже мы сначала отсортировываем по длине слов с помощью OrderBy(). Но их порядок остаётся тот же, что и в коллекции. Чтобы отсортировать их по алфавиту, мы используем ThenBy(). Получается, что сначала по алфавиту отсортировываются слова по 5 символов, далее по 6 символов и т.д.
C#:
string[] fruits = { "grape", "passionfruit", "banana", "mango",
                      "orange", "raspberry", "apple", "blueberry" };

var result = fruits.OrderBy(x=> x.Length); //grape, mango, apple, banana, orange, raspberry, blueberry, passionfruit
var result2 = result.ThenBy(x=>x); //apple, grape, mango, banana, orange, blueberry, raspberry, passionfruit
foreach(var s in result2) project.SendInfoToLog(s);
public static System.Linq.IOrderedEnumerable<TSource> ThenByDescending<TSource,TKey> (this System.Linq.IOrderedEnumerable<TSource> source, Func<TSource,TKey> keySelector) – аналогичен ThenBy, только сортировка будет в обратном порядке
C#:
string[] fruits = { "grape", "passionfruit", "banana", "mango",
                      "orange", "raspberry", "apple", "blueberry" };

var result = fruits.OrderBy(x=> x.Length); //grape, mango, apple, banana, orange, raspberry, blueberry, passionfruit
var result2 = result.ThenByDescending(x=>x); //mango, grape, apple, orange, banana, raspberry, blueberry, passionfruit
foreach(var s in result2) project.SendInfoToLog(s);
public static TSource[] ToArray<TSource> (this System.Collections.Generic.IEnumerable<TSource> source) – конвертирует коллекцию в массив

public static System.Collections.Generic.Dictionary<TKey,TValue> ToDictionary<TKey,TValue>(…) - конвертирует коллекцию в словарь.

public static System.Collections.Generic.HashSet<TSource> ToHashSet<TSource> (this System.Collections.Generic.IEnumerable<TSource> source) – конвертирует коллекцию в HashSet.

public static System.Collections.Generic.List<TSource> ToList<TSource> (this System.Collections.Generic.IEnumerable<TSource> source) – конвертирует коллекцию в список

public static System.Collections.Generic.IEnumerable<TSource> Union<TSource> (this System.Collections.Generic.IEnumerable<TSource> first, System.Collections.Generic.IEnumerable<TSource> second) – объединяет две коллекции, отбрасывая дубли.
C#:
int[] ints1 = { 5, 3, 9, 7, 5, 9, 3, 7 };
int[] ints2 = { 8, 3, 6, 4, 4, 9, 1, 0 };

IEnumerable<int> union = ints1.Union(ints2);

foreach (int num in union)
{
    project.SendInfoToLog(num.ToString());
}
public static System.Collections.Generic.IEnumerable<TSource> Where<TSource> (this System.Collections.Generic.IEnumerable<TSource> source, Func<TSource,bool> predicate) – фильтрует коллекцию. Вторая перегрузка позволяет использовать индекс
C#:
List<string> fruits =
    new List<string> { "apple", "passionfruit", "banana", "mango",
                    "orange", "blueberry", "grape", "strawberry" };

IEnumerable<string> query = fruits.Where(fruit => fruit.Length < 6);

foreach (string fruit in query)
{
    project.SendInfoToLog(fruit); //apple, mango, grape
}
C#:
List<string> fruits =
    new List<string> { "apple", "passionfruit", "banana", "mango",
                    "orange", "blueberry", "grape", "strawberry" };

IEnumerable<string> query = fruits.Where((fruit, index) => fruit.Length > 6 + index);

foreach (string fruit in query)
{
    project.SendInfoToLog(fruit); //passionfruit
}
GroupBy(…) – позволяет группировать элементы по какому-то признаку.
C#:
// Создаём список фруктов
var fruits = new List<string>
{
    "баклажан", "яблоко", "апельсин", "ананас", "банан", "груша", "персик", "паприка", "баран"
};

// Группируем фрукты по первой букве
var groups = fruits.GroupBy(fruit => fruit[0]);

foreach (var group in groups)
{
    foreach (var fruit in group )
        project.SendInfoToLog($"{group.Key} : {fruit} ");
}
/*
   
    б : баклажан
    б : банан
    б : баран
    я : яблоко
    а : апельсин
    а : ананас
    г : груша
    п : персик
    п : паприка */
public static System.Collections.Generic.IEnumerable<TResult> Join<TOuter,TInner,TKey,TResult>(…) – Join используется для объединения элементов двух последовательностей по определённому признаку. Это позволяет анализировать данные и находить взаимосвязи между ними. Вот как можно использовать join в LINQ: Выбрать элементы из первой последовательности. Найти соответствующие элементы во второй последовательности на основе общего признака. Объединить выбранные элементы в одну последовательность. Применить функцию к каждой паре элементов, чтобы получить результат. Использовать полученные результаты для дальнейшей обработки или отображения.

C#:
// Создаём список фруктов
var fruits = new List<string>
{
    "яблоко", "апельсин", "банан", "груша", "персик", "123456", "789012"
};

// Создаём список цветов
var colors = new List<string>
{
    "красный", "оранжевый", "жёлтый", "зелёный", "голубой", "asdfgh"
};

// Соединяем фрукты и цвета по индексу
var joined = fruits.Join(colors, fruit => fruit.Length, color => color.Length, (fruit, color) => new
    {
        Fruit = fruit, Color = color
    });

foreach (var item in joined)
    project.SendInfoToLog($"{item.Fruit} - {item.Color}");

/*

    яблоко - жёлтый
    яблоко - asdfgh
    персик - жёлтый
    персик - asdfgh
    123456 - жёлтый
    123456 - asdfgh
    789012 - жёлтый
    789012 - asdfgh
*/
Заключение

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

 

Вложения

Последнее редактирование модератором:

volody00

Client
Регистрация
06.09.2016
Сообщения
896
Благодарностей
915
Баллы
93

Кто просматривает тему: (Всего: 1, Пользователи: 0, Гости: 1)