LINQ - одна из самых полезных библиотек C # для оптимизации кода, она может сделать ваш код короче, яснее и читабельнее. Для понимания концепций LINQ вы должны сначала понять что такое делегаты и расширенные функции.
Что такое Linq?
LINQ - это системная библиотека, которая добавляет расширения для интерфейса IEnumerable. В LINQ мы можем найти множество делегированных функций, которые предоставляют нам структуры полезных функций, которые мы можем приспособить для наших нужд с помощью делегатов.
Примером расширения LINQ является функция Sum. В качестве примера возьмем класс Builder.
public class Builder { public string builderName { get; set; } public double builderSalary { get; set; } public int builderAge { get; set; } }
Без LINQ, если бы мы хотели просуммировать все зарплаты строителей, нам пришлось бы сделать следующее:
public static double GetFullSalary(Builder[] builders_) { double fullSalary = 0.0d; for(int i = 0; i < builders_.Length; i++) { fullSalary += builders_[i].builderSalary; } return fullSalary; }
Однако в Linq уже есть расширенная функция Sum, которая имеет ту же структуру, что и GetFullSalary.
Следующий код - это мое предложение для реальной реализации функции C # Sum, и это не обязательно реальная реализация
/* Because the following code contains some advanced concepts here is a shortcut of what it means: Step 1 - We create an extention for IEnumerable<T>. Step 2 - We check to see if the struct we accepted is a numeric type or not. If it's not a numeric type we check if the class\struct supports the addition operator using operator overloading, if both cases fail then we return the default value of the type (Step 2 - Everything between the region "Check_If_Its_Number"). Step 3 - After we know it's a numeric type or a class\struct that supports addition we can sum all the elements and return the result. */ public static class LINQExtensions { public static T Sum<T>(this IEnumerable<T> li, Func<T, T> func_) where T: struct //we use T:struct because numerics are //structs { T full = default; //gives 'full' the default value of T #region Check_If_Its_Number try { sbyte mySByte = (sbyte)((dynamic)(default(T))); //if we can cast to sbyte then it's a number type. } catch { //if the class overloads the addition operator we can also make a sum if(!(typeof(T).GetMethods().Any(x => x.Name == "op_Addition"))) /*Any is a LINQ function that checks in that case if there is a function called op_Addition which means that the class/struct supports the addition operator.*/ { Console.WriteLine("The following object cannot use addition operator."); return default(T); /*if can't cast and the class/struct doesn't support addition return the default value of T*/ } } #endregion for (int i = 0; i < li.Count(); i++) { full += (dynamic)(func_(li.ElementAt(i))); //sums all the elements } return full; } }
Это означает, что вместо того, чтобы писать функцию GetFullSalary, мы могли бы просто написать.
using System.Linq; ... double fullSalary = builders_.Sum(x => x.builderSalary);
Как вы можете видеть в приведенном выше примере, расширение LINQ упростило нам задачу, потому что мы могли использовать его, не создавая функцию GetFullSalary.
Дополнительные структуры LINQ
Выбирать
Функция выбора возвращает нам новый IEnumerable с размером вызывающего IEnumerable. Функция выполняет операцию делегирования для каждого элемента в IEnumerable.
Builder[] builders = { new Builder() { builderName = "David" }, new Builder() { builderName = "John" }, new Builder() { builderName = "Mikel"} }; string[] builderNames = builders.Select(x => x.builderName).ToArray(); //builderName = ["David", "John", "Mikel"] /*--Possible Implementation of Select method-- public static class LINQExtensions { public static IEnumerable<TResult> Select<T,TResult>(this IEnumerable<T> li, Func<T, TResult> func_) { foreach(var item in li) { yield return func_(item);//adds current item to returned IEnumerable } } }*/
В приведенном выше примере для каждого построителя в массиве мы возвращаем его имя.
Где
Функция where фильтрует элементы в IEnumerable и возвращает новый IEnumerable, содержащий только элемент, который передал условие.
Builder[] builders = { new Builder() { builderName = "David", builderSalary = 45.0d }, new Builder() { builderName = "Mark", builderSalary = 450_000 }, new Builder() { builderName = "Smith", builderSalary = 100_000} }; IEnumerable<Builder> a = builders.Where(x => x.builderSalary < 100).ToArray(); // a will contain only David because he is the only one with a salary that's below 100 /*--possible implementation of Where method-- public static class LINQExtensions { public static IEnumerable<T> Where<T>(this IEnumerable<T> li, Func<T, bool> func_) { foreach (var item in li) { if(func_(item)) yield return item;//adds current item to the returned IEnumerable } } }*/
Любые и все
Любая функция возвращает истину, если в IEnumerable есть элемент, поддерживающий условие. Если Any вызывается без параметров, он определяет, равна ли длина IEnumerable 0.
Функция All возвращает истину, если все элементы в IEnumerable удовлетворяют условию.
int[] arr = {1,2,3,4}; bool biggerThan5 = arr.Any(x => x > 5); //false bool any = arr.Any(); //true bool all = arr.All(x => x < 5); //true
Макс. И мин. И среднее
Функции max и min возвращают максимальное / минимальное значение в IEnumerable.
Среднее значение возвращает среднее значение элементов.
int[] arr = { 1, 2, 30, 100, 5, 10 }; int max_value = arr.Max(); //100 int min_value = arr.Min(); //1 int average_ = arr.Average(); int minSalaryBuilder = builders.Min(x => x.builderSalary); //45 int maxSalaryBuilder = builders.Max(x => x.builderSalary); //450,000 double averageSalary = builders.Average(x => x.builderSalary); //183348.333333
В прошлом первый
Возвращает последнее / первое вхождение условия, если нет условия, будет возвращен последний / первый элемент IEnumerable.
int[] arr = { 1, 2, 3, 4, 5, 100, 200, 3, 50 }; int last_ = arr.Last(x => x > 50); //200 int last_element = arr.Last(); // 50 int first_ = arr.First(x => x > 50); //100 int first_element = arr.First(); //1
Пересечение
Функция Intersect принимает IEnumerable в качестве параметра и возвращает новый IEnumerable, в котором остаются только элементы, общие для обоих списков.
Порядок общих элементов в новом списке будет в том же порядке, что и вызывающий IEnumerable (не параметр).
int[] arr = { 1, 2, 3, 4, 6 }; int[] other_ = { 1, 2, 3, 4, 5 }; arr = arr.Intersect(other_).ToArray(); //[1,2,3,4] arr = { 1, 2, 3, 5, 4, 6 }; other_ = { 6 , 2, 3, 4, 7 }; arr = arr.Intersect(other_); //[2,3,4,6]
Добавить и добавить
Функция Prepend принимает параметр и возвращает новый IEnumerable, где параметр находится в начале IEnumerable.
Функция Append принимает параметр и возвращает новый IEnumerable, где параметр находится в конце IEnumerable.
int[] arr = { 1, 2, 3, 4 }; arr = arr.Prepend(0).ToArray(); //arr = [0,1,2,3,4] arr = arr.Append(5).ToArray(); //arr = [0,1,2,3,4,5]
Группа по
Функция GroupBy позволяет нам создавать группы элементов с одним и тем же идентификатором. Например, если у нас есть список вакансий, мы можем составить группу программистов и группу строителей.
Person[] arr = { new Person(){ job_ = Job.Builder, Name = "Dan"}, new Person(){ job_ = Job.Builder, Name = "Frank"}, new Person(){ job_ = Job.Programmer, Name = "Dave"}, }; var group_ = arr.GroupBy(x => x.job_).ToDictionary(x => x.Key); /*group_ = [(Job.Builder):[Person Dan, Person Frank], (Job.Programmer):[Person Dave]]*/
Сортировать по
Функция OrderBy сортирует IEnumerable по параметру делегата в порядке возрастания.
int[] myArr = {4,3,2,1}; myArr = myArr.OrderBy(x => x).ToArray(); //[1,2,3,4] Builder[] arr = { new Builder() { Name = "David", Salary = 180 }, new Builder() { Name = "Frank", Salary = 120 }, new Builder() { Name = "Jake", Salary = 150 }, }; arr = arr.OrderBy(x => x.Salary).ToArray(); /*[Builder Frank, Builder Jake, Builder David]*/
OrderByDescending
Функция OrderByDescending сортирует IEnumerable по параметру делегата в порядке убывания.
int[] myArr = {1,2,3,4}; myArr = myArr.OrderByDescending(x => x).ToArray(); //[4,3,2,1] Builder[] arr = { new Builder() { Name = "David", Salary = 180 }, new Builder() { Name = "Frank", Salary = 120 }, new Builder() { Name = "Jake", Salary = 150 }, }; arr = arr.OrderByDescending(x => x.Salary).ToArray(); //[Builder David, Builder Jake, Builder Frank]
Функции LINQ без делегатов
Отчетливый
Функция Distinct удаляет все повторяющиеся элементы.
int[] arr = {1,2,1}; arr = arr.Distinct().ToArray(); //[1,2]
Задний ход
Функция Reverse меняет позиции элементов в IEnumerable.
int[] arr = {1,5,6,7}; int[] reversed_ = arr.Reverse().ToArray(); //[7,6,5,1]
LINQ запросы
Мы можем использовать ключевые слова, предлагаемые LINQ, для выполнения запросов Linq.
Приведем пример:
int[] arr = { 1, 2, 3, 4, 5 }; var get_element = from i in arr select i; //---------------------------------------------------- //Equivalent to public static IEnumerable<T> Copy<T>(IEnumerable<T> arr) { foreach(var item in arr) { yield return item; } }
В приведенном выше примере i - это элемент в цикле foreach, arr - это IEnumerable, для которого мы выполняем цикл foreach и выбираем элемент на каждой итерации.
В результате приведенный выше пример просто скопирует элементы arr в get_element.
Ключевое слово ‘where’ может помочь нам отфильтровать элементы и поместить в новый IEnumerable только те элементы, которые соответствуют условию.
int[] arr = { 1, 2, 3, 4, 5 }; var new_ = from i in arr where i < 3 select i; //[1,2]
Ключевое слово «let» позволяет нам создать дополнительную переменную в запросе.
int[] arr = {1, 2, 3, 4, 5}; var new_ = from i in arr where i < 3 let num_ = i + 1 select num_; //[2,3]
Ключевое слово «orderby» позволяет нам упорядочивать элементы в IEnumerable по возрастанию \ убыванию.
int[] arr = {1, 2, 3, 10, 5}; var new_ = from i in arr orderby i descending select i; //[10,5,3,2,1] new_ = from i in arr orderby i ascending select i + 1;//[2,3,4,6,11] new_ = from i in arr where i < 3 orderby i descending select i + 1;//[3,2]
Ключевые слова «group & by» позволяют сгруппировать IEnumerable по ключу.
`Person[] myPerson = { new Person(){ job_ = Job.Builder, Name = "Mike" }, new Person(){ job_ = Job.Builder, Name = "Jack"}, new Person(){ job_ = Job.Teacher, Name = "David"}, new Person(){ job_ = Job.Programmer, Name = "Ben"} }; var groups_ = (from i in myPerson group i by i.job_).ToDictionary(x => x.Key); /*Now we have a group of Builders, Programmers and teachers. [(Job.Builder):[Person Mike, Person Jack], (Job.Teacher):[Person David],(Job.Programmer):[Person Ben]]*/
Мы можем использовать ключевое слово «into», чтобы сохранить значение группы в переменной запроса.
var groups_ = (from i in myPerson group i by i.job_ into myProj //myProj stores group where myProj.Count() == 1 select myProj).ToDictionary(x => x.Key); //[(Job.Teacher):[Person David],(Job.Programmer):[Person Ben]]
Поздравляю! Вы закончили знакомство с LINQ! :)
Уровень кодирования
Спасибо, что стали частью нашего сообщества! Подпишитесь на наш канал YouTube или присоединитесь к Интервью по программированию Skilled.dev.