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