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.