Полное руководство по языку программирования С# 14 и платформе .NET 10 / LINQ

Проекция данных

Начальный ⏱ 6 мин чтения 👁 2 🕓 обновлено 11.08.2026

Проекция позволяет преобразовать объект одного типа в объект другого типа. Для проекции используется оператор select. Допустим, у нас есть набор объектов следующего класса, представляющего пользователя:


record class Person(string Name, int Age);

Но, допустим, нам нужен не весь объект, а только его свойство Name:


var people = new List<Person>
{
    new Person ("Tom", 23),
    new Person ("Bob", 27),
    new Person ("Sam", 29),
    new Person ("Alice", 24)
};

var names = from p in people select p.Name;

foreach (string n in names)
     Console.WriteLine(n);

Результат выражения LINQ будет представлять набор строк, поскольку выражение select p.Name выбирает в результирующую выборку только значения свойства Name.


Tom
Bob
Sam
Alice

В качестве альтернативы мы могли бы использовать метод расширения Select():

Select(Func<TSource,TResult> selector)

Этот метод принимает функцию преобразования в виде делегата Func<TSource,TResult>. Функция преобразования получает каждый объект выборки типа TSource и с его помощью создает объект TResult. Метод Select возвращает коллекцию преобразованных объектов.

Перепишем предыдущий пример с применением метода Select:


var people = new List<Person>
{
    new Person ("Tom", 23),
    new Person ("Bob", 27),
    new Person ("Sam", 29),
    new Person ("Alice", 24)
};
var names = people.Select(u => u.Name);

foreach (string n in names)
     Console.WriteLine(n);

Аналогично можно создать объекты другого типа, в том числе анонимного:


var people = new List<Person>
{
    new Person ("Tom", 23),
    new Person ("Bob", 27)
};

var personel = from p in people
            select new
            {
                FirstName = p.Name,
                Year = DateTime.Now.Year - p.Age
            };

foreach (var p in personel)
    Console.WriteLine($"{p.FirstName} - {p.Year}");


record class Person(string Name, int Age);

Здесь оператор select создает объект анонимного типа, используя текущий объект Person. И теперь результат будет содержать набор объектов данного анонимного типа, в котором определены два свойства: FirstName и Year (год рождения). Консольный вывод программы:


Tom - 1999
Bob - 1995

В качестве альтернативы мы могли бы использовать метод расширения Select():


// проекция на объекты анонимного типа
var personel = people.Select(p => new 
{ 
    FirstName = p.Name, 
    Year = DateTime.Now.Year - p.Age 
});

Переменые в запросах и оператор let

Иногда возникает необходимость произвести в запросах LINQ какие-то дополнительные промежуточные вычисления. Для этих целей мы можем задать в запросах свои переменные с помощью оператора let:


var people = new List<Person>
{
    new Person ("Tom", 23),
    new Person ("Bob", 27)
};

var personnel = from p in people
               let name = $"Mr. {p.Name}"
               let year = DateTime.Now.Year - p.Age
               select new
            {
                Name = name,
                Year = year
            };

foreach (var p in personnel)
    Console.WriteLine($"{p.Name} - {p.Year}");

record class Person(string Name, int Age);

В данном случае создаются две переменных. Переменная name, значение которой равно $"Mr. {p.Name}".

Возможность определения переменных наверное одно из главных преимуществ операторов LINQ по сравнению с методами расширения.

Выборка из нескольких источников

В LINQ можно выбирать объекты не только из одного, но и из большего количества источников. Например, возьмем классы:


record class Course(string Title);  // учебный курс
record class Student(string Name);  // студент

Класс Course представляет учебный курс и хранит его название. Класс Student представляет студента и хранит его имя.

Допустим, нам надо из списка курсов и списка студентов получить набор пар студент-курс (условно говоря сущность, которая представляет учебу студента на данном курсе):


var courses = new List<Course> { new Course("C#"), new Course("Java") };
var students = new List<Student> { new Student("Tom"), new Student("Bob") };

var enrollments = from course in courses    //  выбираем по одному курсу
             from student in students       //  выбираем по одному студенту
             select new { Student = student.Name, Course = course.Title};   // соединяем каждого студента с каждым курсом

foreach (var enrollment in enrollments)
    Console.WriteLine($"{enrollment.Student} - {enrollment.Course}");

record class Course(string Title);  // учебный курс
record class Student(string Name);  // студент

Консольный вывод:

Tom - C#
Bob - C#
Tom - Java
Bob - Java

Таким образом, при выборке из двух источников каждый элемент из первого источника будет сопоставляться с каждым элементом из второго источника. То есть получиться 4 пары.

SelectMany и сведение объектов

Метод SelectMany позволяет свести набор коллекций в одну коллекцию. Он имеет ряд перегруженных версий. Возьмем одну из них:


SelectMany(Func<TSource, IEnumerable<TResult>> selector);
SelectMany(Func<TSource, IEnumerable<TCollection>> collectionSelector, Func<TSource,TCollection,TResult> resultSelector);

Первая версия метода принимает функцию преобразования в виде делегата Func<TSource,IEnumerable<TResult>>. Функция преобразования получает каждый объект выборки типа TSource и с его помощью создает набор объектов TResult. Метод SelectMany возвращает коллекцию преобразованных объектов.

Вторая версия принимает функцию преобразования в виде делегата Func<TSource,IEnumerable<TResult>>. Функция преобразования получает каждый объект выборки типа TSource и возвращает некоторую промежуточную коллекцию типа TCollection. Второй параметр - то же функция функция преобразования в виде делегата Func<TSource,TCollection,TResult>, которая получает два параметра - каждый элемент текущей выборки и каждый элемент промежуточной коллекции и на их основе создает некоторый объект типа TResult.

Рассмотрим следующий пример:


var companies = new List<Company>
{
    new Company("Microsoft", new List<Person> {new Person("Tom"), new Person("Bob")}),
    new Company("Google", new List<Person> {new Person("Sam"), new Person("Mike")}),
};
var employees = companies.SelectMany(c => c.Staff);

foreach (var emp in employees)
    Console.WriteLine($"{emp.Name}");

record class Company(string Name, List<Person> Staff);
record class Person(string Name);

Здесь нам дан список компаний, в каждой компании имеет набор сотрудников в виде списка объектов Person. И на выходе мы получаем список сотрудников всех компаний, то есть по сути коллекцию объектов Person. Консольный вывод:

Tom
Bob
Sam
Mike

Аналогичный пример с помощью операторов LINQ:


var companies = new List<Company>
{
    new Company("Microsoft", new List<Person> {new Person("Tom"), new Person("Bob")}),
    new Company("Google", new List<Person> {new Person("Sam"), new Person("Mike")}),
};
var employees = from c in companies
                from emp in c.Staff
                select emp;

foreach (var emp in employees)
    Console.WriteLine($"{emp.Name}");

record class Company(string Name, List<Person> Staff);
record class Person(string Name);

Теперь добавим к сотрудникам их компанию:


var companies = new List<Company>
{
    new Company("Microsoft", new List<Person> {new Person("Tom"), new Person("Bob")}),
    new Company("Google", new List<Person> {new Person("Sam"), new Person("Mike")}),
};

var employees = companies.SelectMany(c => c.Staff,
                                    (c, emp)=> new { Name = emp.Name, Company = c.Name });

foreach (var emp in employees)
    Console.WriteLine($"{emp.Name} - {emp.Company}");

record class Company(string Name, List<Person> Staff);
record class Person(string Name);

Здесь применяется другая версия метода SelectMany. Первый делегат в виде c => c.Staff создает промежуточную коллекцию - фактически просто возвращаем набор сотрудников каждой компании. Второй делегат - (c, emp)=> new { Name = emp.Name, Company = c.Name } получает каждую компанию и каждый элемент промежуточной коллекции - объект Person и на их основе создает анонимный объект с двумя свойствами Name и Company. Консольный вывод программы:

Tom - Microsoft
Bob - Microsoft
Sam - Google
Mike - Google

Аналогичный пример с помощью операторов запросов:


var companies = new List<Company>
{
    new Company("Microsoft", new List<Person> {new Person("Tom"), new Person("Bob")}),
    new Company("Google", new List<Person> {new Person("Sam"), new Person("Mike")}),
};
var employees = from c in companies
             from emp in c.Staff
             select new { Name = emp.Name, Company = c.Name };

foreach (var emp in employees)
    Console.WriteLine($"{emp.Name} - {emp.Company}");

record class Company(string Name, List<Person> Staff);
record class Person(string Name);

Проверь себя

📋 Тест по теме «Проекция данных» Тест по теме

Практические задания

Лёгкое Ручная проверка Заполните пропуск в коде (1)
+10 XP

В приведённом ниже фрагменте кода из этого урока пропущено одно слово. Определите его и впишите в поле ответа (без кавычек и пробелов).

_____ class Person(string Name, int Age);
Войдите, чтобы отправить решение
Лёгкое Ручная проверка Заполните пропуск в коде (2)
+10 XP

В приведённом ниже фрагменте кода из этого урока пропущено одно слово. Определите его и впишите в поле ответа (без кавычек и пробелов).

var people = _____ List<Person>
{
    new Person ("Tom", 23),
    new Person ("Bob", 27),
    new Person ("Sam", 29),
    new Person ("Alice", 24)
};

var names = from p in people select p.Name;

foreach (string n in names)
     Console.WriteLine(n);
Войдите, чтобы отправить решение
Лёгкое Ручная проверка Заполните пропуск в коде (3)
+10 XP

В приведённом ниже фрагменте кода из этого урока пропущено одно слово. Определите его и впишите в поле ответа (без кавычек и пробелов).

Tom
Bob
_____
Alice
Войдите, чтобы отправить решение
Лёгкое Ручная проверка Заполните пропуск в коде (4)
+10 XP

В приведённом ниже фрагменте кода из этого урока пропущено одно слово. Определите его и впишите в поле ответа (без кавычек и пробелов).

Select(Func<TSource,_____> selector)
Войдите, чтобы отправить решение
Лёгкое Ручная проверка Заполните пропуск в коде (5)
+10 XP

В приведённом ниже фрагменте кода из этого урока пропущено одно слово. Определите его и впишите в поле ответа (без кавычек и пробелов).

var people = _____ List<Person>
{
    new Person ("Tom", 23),
    new Person ("Bob", 27),
    new Person ("Sam", 29),
    new Person ("Alice", 24)
};
var names = people.Select(u => u.Name);

foreach (string n in names)
     Console.WriteLine(n);
Войдите, чтобы отправить решение
Среднее Ручная проверка Восстановите код по памяти (1)
+20 XP

В этом фрагменте кода урока пропущены два ключевых элемента. Впишите оба пропущенных значения через запятую в порядке появления (например: x, 5).

record class _____(string _____, int Age);
Войдите, чтобы отправить решение
Среднее Ручная проверка Восстановите код по памяти (2)
+20 XP

В этом фрагменте кода урока пропущены два ключевых элемента. Впишите оба пропущенных значения через запятую в порядке появления (например: x, 5).

var people = new List<Person>
{
    new Person ("Tom", 23),
    new Person ("Bob", 27),
    new Person ("Sam", _____),
    new Person ("Alice", 24)
};

var names = from p in people _____ p.Name;

foreach (string n in names)
     Console.WriteLine(n);
Войдите, чтобы отправить решение
Среднее Ручная проверка Расставьте строки кода в правильном порядке
+20 XP

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

Alice
Tom
Bob
Sam
Войдите, чтобы отправить решение
Сложное Ручная проверка Объясните и модифицируйте пример из урока «Проекция данных»
+30 XP

Опишите своими словами, что делает приведённый пример кода из урока «Проекция данных», и предложите одно осмысленное изменение (например, другое входное значение или дополнительное условие). Приведите итоговый код.

record class Person(string Name, int Age);
Войдите, чтобы отправить решение
Сложное Ручная проверка Найдите потенциальную проблему в коде урока «Проекция данных»
+30 XP

Изучите приведённый фрагмент кода и опишите, при каких входных данных или условиях он может сработать не так, как ожидается (граничный случай, отсутствие проверки и т.п.). Предложите исправленный вариант.

var people = new List<Person>
{
    new Person ("Tom", 23),
    new Person ("Bob", 27),
    new Person ("Sam", 29),
    new Person ("Alice", 24)
};

var names = from p in people select p.Name;

foreach (string n in names)
     Console.WriteLine(n);
Войдите, чтобы отправить решение
Мини-проект Ручная проверка Мини-проект по теме «Проекция данных»
+50 XP

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

Войдите, чтобы отправить решение
Домашнее задание Ручная проверка Домашнее задание по теме «Проекция данных»
+25 XP

Напишите собственную небольшую программу, применяющую материал темы «Проекция данных». Используйте не менее одной конструкции, показанной в примерах урока. Прикрепите код решения и краткое описание того, что он делает.

Войдите, чтобы отправить решение

Обсуждение (0)

Войдите, чтобы оставить комментарий.