В прошлый раз мы познакомились с основными возможностями 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
. Если этого не сделать, то программа не скомпилируется и вы получите исключение следующего содержания:
С использованием метода расширения 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
определили свой тип данных, основанный на свойствах объектов из набора данных. В результате в консоли мы увидим следующую информацию:
Ваня 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
мы можем определить свой анонимный тип, используемый в результирующей выборки значений и, при этом, набор свойств анонимного типа может не совпадать с набором свойств объектов из которых состоит исходный набор данных.