§16.3 Фильтрация выборки и проекция
Фильтрация
Для выбора элементов из некоторого набора по условию используется метод Where. Например, выберем все четные элементы, которые больше 10.
Фильтрация с помощью операторов LINQ:
Листинг 16.4
1int[] numbers = { 1, 2, 3, 4, 10, 34, 55, 66, 77, 88 };
2IEnumerable<int> evens = from i in numbers
3 |
where i%2==0 && i>10 |
4 |
select i; |
5foreach (int i in evens)
6Console.WriteLine(i);
Здесь используется конструкция from: from i in numbers.
Тот же запрос с помощью метода расширения:
Листинг 16.5
1int[] numbers = { 1, 2, 3, 4, 10, 34, 55, 66, 77, 88
}
2IEnumerable<int> evens = numbers.Where(i => i % 2 == 0 && i > 10)
Если выражение в методе Where для определенного элемента будет равно true (в данном случае выражение i % 2 == 0 && i > 10), то данный элемент попадает в результирующую выборку.
Выборка сложных объектов
Допустим, у нас есть класс пользователя:
|
|
Листинг 16.6 |
|
|
|
|
|
1 |
class User |
|
|
2 |
{ |
|
|
3 |
public |
string Name { get;set; } |
|
4 |
public |
int Age { get; set; } |
|
5 |
public |
List<string> Languages { get; set; } |
|
6 |
public |
User() |
|
7 |
{ |
|
|
8 |
Languages = new List<string>(); |
|
|
9 |
} |
|
|
|
|
6 |
|
10 }
Создадим набор пользователей и выберем из них тех, которым больше
25 лет:
Листинг 16.7
1 |
List<User> users = new List<User> |
|
2 |
{ |
|
3 |
new User {Name="Том", Age=23, Languages = new |
|
List<string> {"английский", "немецкий" }}, |
||
|
||
4 |
new User {Name="Боб", Age=27, Languages = new |
|
List<string> {"английский", "французский" }}, |
||
|
||
5 |
new User {Name="Джон", Age=29, Languages = new |
|
List<string> {"английский", "испанский" }}, |
||
|
||
6 |
new User {Name="Элис", Age=24, Languages = new |
|
List<string> {"испанский", "немецкий" }} |
||
|
||
7 |
}; |
|
8 |
var selectedUsers = from user in users |
|
9 |
where user.Age > 25 |
|
10 |
select user; |
|
11 |
foreach (User user in selectedUsers) |
|
12 |
Console.WriteLine($"{user.Name} - {user.Age}"); |
|
|
Аналогичный запрос с помощью метода расширения Where: |
var selectedUsers = users.Where(u=> u.Age > 25);
Сложные фильтры
Теперь рассмотрим более сложные фильтры. Например, в классе пользователя есть список языков, которыми владеет пользователь. Что если нам надо отфильтровать пользователей по языку:
Листинг 16.8
1 var selectedUsers = from user in users
2 from lang in user.Languages
3 where user.Age < 28
4 where lang == "английский"
5 select user;
Для создания аналогичного запроса с помощью методов расширения
применяется метод SelectMany:
Листинг 16.9
1 var selectedUsers = users.SelectMany(u => u.Languages,
7
2 |
(u, l) => new { User = u, Lang = l }) |
3 |
.Where(u => u.Lang == "английский" && u.User.Age < 28) |
4 |
.Select(u=>u.User); |
Метод SelectMany() в качестве первого параметра принимает последовательность, которую надо проецировать, а в качестве второго параметра - функцию преобразования, которая применяется к каждому элементу. На выходе она возвращает 8 пар "пользователь - язык"
(new { User = u, Lang = l }),
к которым потом применяется фильтр с помощью Where.
Проекция
Проекция позволяет спроектировать из текущего типа выборки какой-то другой тип. Для проекции используется оператор select. Допустим, у нас есть набор объектов следующего класса, представляющего пользователя:
Листинг 16.10
1 |
class User |
|
2 |
{ |
|
3 |
public string Name { get;set; } |
|
4 |
public int Age { get; set; } |
|
5 |
} |
|
|
Но нам нужен не весь объект, а только его свойство Name: |
|
|
Листинг 16.11 |
|
|
|
|
1 |
List<User> users = new List<User>(); |
|
2 |
users.Add(new User { Name = "Sam", Age = 43 }); |
|
3 |
users.Add(new User { Name = "Tom", Age = 33 }); |
|
4 |
var names = from u in users select u.Name; |
|
5 |
foreach (string n in names) |
|
6 |
Console.WriteLine(n); |
|
Результат выражения LINQ будет представлять набор строк, поскольку выражение select u.Name выбирают в результирующую выборку только значения свойства Name.
8
Аналогично можно создать объекты другого типа, в том числе
анонимного:
|
Листинг 16.12 |
|
|
|
|
1 |
List<User> users = new List<User>(); |
|
2 |
users.Add(new User { Name = "Sam", Age = 43 }); |
|
3 |
users.Add(new User { Name = "Tom", Age = 33 }); |
|
4 |
var items = from u in users |
|
5 |
select new |
|
6 |
{ |
|
7 |
FirstName = u.Name, |
|
8 |
DateOfBirth = DateTime.Now.Year - |
|
u.Age |
|
|
|
|
|
9 |
}; |
|
10 |
foreach (var n in items) |
|
11 |
Console.WriteLine($"{n.FirstName} - {n.DateOfBirth}"); |
|
Здесь оператор select создает объект анонимного типа, используя текущий объект User. И теперь результат будет содержать набор объектов данного анонимного типа, в котором определены два свойства: FirstName и DateOfBirth.
В качестве альтернативы мы могли бы использовать метод расширения
Select():
Листинг 16.13
1 |
// выборка имен |
|
2 |
var names = users.Select(u => u.Name); |
|
3 |
// выборка объектов анонимного типа |
|
4 |
var items = users.Select(u => new |
|
5 |
{ |
|
6 |
FirstName = |
u.Name, |
7 |
DateOfBirth |
= DateTime.Now.Year - u.Age |
8 |
}); |
|
9
Выборка из нескольких источников
В LINQ можно выбирать объекты не только из одного, но и из большего
количества источников.
|
Например, возьмем классы: |
|
|
Листинг 16.14 |
|
|
|
|
1 |
class Phone |
|
2 |
{ |
|
3 |
public string Name { get; set; } |
|
4 |
public string Company { get; set; } |
|
5 |
} |
|
6 |
class User |
|
7 |
{ |
|
8 |
public string Name { get; set; } |
|
9 |
public int Age { get; set; } |
|
10 |
} |
|
|
Создадим два разных источника данных и произведем выборку: |
|
|
Листинг 16.15 |
|
|
|
|
1 |
List<User> users = new List<User>() |
|
2 |
{ |
|
3 |
new User { Name = "Sam", Age = 43 }, |
|
4 |
new User { Name = "Tom", Age = 33 } |
|
5 |
}; |
|
6 |
List<Phone> phones = new List<Phone>() |
|
7 |
{ |
|
8 |
new Phone {Name="Lumia 630", Company="Microsoft" |
|
}, |
|
|
|
|
|
9 |
new Phone {Name="iPhone 6", Company="Apple"}, |
|
10 |
}; |
|
11 |
var people = from user in users |
|
12 |
from phone in phones |
|
13 |
select new { Name = user.Name, Phone = |
|
phone.Name }; |
|
|
14 |
foreach (var p in people) |
|
15 |
Console.WriteLine($"{p.Name} - {p.Phone}"); |
|
|
Таким образом, при выборке из двух источников каждый элемент из |
|
первого источника будет сопоставляться с каждым элементом из второго источника. То есть должно получиться четыре пары.
10