LINQ в C#: проекции и фильтрация выборки

В прошлый раз мы познакомились с основными возможностями LINQ и остановились на рассмотрении методов расширения. Учитывая,что SQL-стиль работы с LINQ используется относительно редко, в дальнейшем мы будем рассматривать возможности LINQ в контексте использования именно методов расширения. И начнем мы с проекций и фильтраций выборки.

Методы расширения Where и Select

Метод Where() позволяет отфильтровать набор данных по определенному критерию, а метод Select() позволяет нам определить проекцию выбранных значений. Что это означает — рассмотрим на примере. Допустим у нас есть список, содержащий имена пользователей:

namespace LinqStart
{
    public class Person
    {
        public string Name { get; set; }
        public string Surname { get; set; }
        public byte Age { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            List<Person> people = new List<Person>()
            {
               new Person {Name = "Вася", Surname ="Пупкин", Age = 15 },
               new Person {Name = "Петя", Surname ="Петров", Age = 16 },
               new Person {Name = "Ваня", Surname ="Иванов", Age = 22 },
               new Person {Name = "Гоша", Surname ="Григорьев", Age = 31 },
               new Person {Name = "Никита", Surname ="Иванов", Age = 46 },
            };


        }
    }
}

Используя метод Where мы можем отфильтровать этот список и выбрать, например, всех пользователей у которых имя начинается на букву «В»:

var result = people.Where(p => p.Name.ToUpper().StartsWith("В"));
foreach (Person person in result)
{
    Console.WriteLine($"{person.Name} {person.Surname}");
}

В этом случае в итоговую выборку попадут все объекты класса Person и на экране мы увидим следующий результат:

Вася Пупкин
Ваня Иванов

Использование же метода Select позволяет нам определить анонимный тип на основании которого будет составляться итоговая выборка. Например, мы можем собрать в итоговой выборке только имя и возраст пользователя. В этом случае, код программы будет вот такой:

var result = people.Where(p => p.Name.ToUpper().StartsWith("В"))
                   .Select(p => new { p.Name, p.Age});
foreach (var person in result)
{
    Console.WriteLine($"{person.Name} {person.Age}");
}

Здесь стоит обратить внимание на следующий момент — при определении проекции мы указываем анонимный тип, который совсем не обязательно должен соответствовать по набору свойств объектам в исходном наборе, что, собственно, и продемонстрировано в примере. Соответственно и при выводе результатов в консоль необходимо использовать вместо явного указания типа var. Если этого не сделать, то программа не скомпилируется и вы получите исключение следующего содержания:

Ошибка CS0030 Не удается преобразовать тип «anonymous type: string Name, byte Age» в «Person»

С использованием метода расширения Select мы можем сконструировать вообще другой тип данных, например, добавим в итоговую выборку год рождения человека:

var result = people.Where(p => p.Name.ToUpper().StartsWith("В"))
                   .Select(p => new { Name = p.Name, Age = p.Age, NewProperty = DateTime.Now.Year - p.Age});
foreach (var person in result)
{
    Console.WriteLine($"{person.Name} {person.Age} Родился в {person.NewProperty} году");
}

Здесь мы в методе Select определили свой тип данных, основанный на свойствах объектов из набора данных. В результате в консоли мы увидим следующую информацию:

Вася 15 Родился в 2006 году
Ваня 22 Родился в 1999 году

Сложные фильтры (метод SelectMany)

Объекты, содержащиеся в исходном наборе данных могут содержать в качестве свойств другие объекты, например, списки строк. Например, определим для нашего класса новое свойство:

public class Person
{
    ....
    public List<string> Languages { get; set; } = new List<string>();
}

и заполним список значениями:

List<Person> people = new List<Person>()
{
   new Person {Name = "Вася", Surname ="Пупкин", Age = 15, Languages = { "C#", "Delphi"} },
   new Person {Name = "Петя", Surname ="Петров", Age = 16 , Languages = { "C#", "Java"} },
   new Person {Name = "Ваня", Surname ="Иванов", Age = 22 , Languages = { "Visual Basic", "C++"} },
   new Person {Name = "Гоша", Surname ="Григорьев", Age = 31, Languages = { "C#", "Python"}  },
   new Person {Name = "Никита", Surname ="Иванов", Age = 46, Languages = { "Ruby", "Java Script"}  },
};

Теперь попробуем выбрать из этого списка всех людей, которые старше 16 лет и пишут на C#. Сделать это можно двумя способами:

Первый — использовать уже известный нам метод Where:

var result = people.Where(p => p.Age > 16 && p.Languages.Contains("C#"));
foreach (var person in result)
{
    Console.WriteLine($"{person.Name} пишет на:");
    foreach (string lang in person.Languages)
        Console.WriteLine($"{lang}");
}
Петя пишет на:
C#
Java
Гоша пишет на:
C#
Python

Второй — использовать метод SelectMany, который в первом параметре принимает набор данных, который надо проецировать, а во втором параметре — функцию преобразования, которая применяется к каждому элементу. С использованием метода SelectMany наш код приобретет следующий вид:

var result2 = people.SelectMany(u => u.Languages,
                               (u, l) => new { User = u, Lang = l })
                    .Where(u => u.Lang == "C#" && u.User.Age > 15);

По сути, метод SelectMany дает результат перекрестного соединения двух множеств (их декартово произведение), где первое множество — это наш список people, а второе множество — это список языков (Languages), который передается в качестве первого параметра в методе SelectMany. Затем, к полученному множеству мы также применяем метод Where.

Оба эти варианта фильрации, в данном случае, приводят к одному и тому же результату. поэтому, какой из вариантов выбирать — решать вам.

Итого

Сегодня мы рассмотрели такие методы расширения LINQ как Select, Where и SelectMany, используемые для определения проекций и фильтрации значений. С использованием метода Select мы можем определить свой анонимный тип, используемый в результирующей выборки значений и, при этом, набор свойств анонимного типа может не совпадать с набором свойств объектов из которых состоит исходный набор данных.

Подписаться
Уведомить о
guest
0 Комментарий
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии