Запрос коллекции объектов

Термин "LINQ to Objects" означает использование запросов LINQ с любой коллекцией IEnumerable или IEnumerable<T> напрямую, без привлечения промежуточного поставщика LINQ, API LINQ to SQL или LINQ to XML. Вы можете выполнить запрос LINQ к любой перечислимой коллекции, такой как List<T>, Array или Dictionary<TKey,TValue>. Коллекция может быть определена пользователем или возвращена API .NET. При использовании LINQ пишется декларативный код, описывающий, какие данные необходимо извлечь.

Кроме того, запросы LINQ предлагают три основных преимущества по сравнению с традиционными циклами foreach:

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

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

В этом примере показано, как выполнить простой запрос к списку объектов Student. Каждый объект Student содержит некоторые основные сведения об учащемся, а также список, отражающий баллы, которые он набрал по результатам четырех экзаменов.

Примечание.

Многие другие примеры в этом разделе используют тот же Student класс и students коллекцию.

class Student
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int ID { get; set; }
    public GradeLevel? Year { get; set; }
    public List<int> ExamScores { get; set; }

    public Student(string FirstName, string LastName, int ID, GradeLevel Year, List<int> ExamScores)
    {
        this.FirstName = FirstName;
        this.LastName = LastName;
        this.ID = ID;
        this.Year = Year;
        this.ExamScores = ExamScores;
    }

    public Student(string FirstName, string LastName, int StudentID, List<int>? ExamScores = null)
    {
        this.FirstName = FirstName;
        this.LastName = LastName;
        ID = StudentID;
        this.ExamScores = ExamScores ?? [];
    }

    public static List<Student> students =
    [
        new(
            FirstName: "Terry", LastName: "Adams", ID: 120,
            Year: GradeLevel.SecondYear,
            ExamScores: [99, 82, 81, 79]
        ),
        new(
            "Fadi", "Fakhouri", 116,
            GradeLevel.ThirdYear,
            [99, 86, 90, 94]
        ),
        new(
            "Hanying", "Feng", 117,
            GradeLevel.FirstYear,
            [93, 92, 80, 87]
        ),
        new(
            "Cesar", "Garcia", 114,
            GradeLevel.FourthYear,
            [97, 89, 85, 82]
        ),
        new(
            "Debra", "Garcia", 115,
            GradeLevel.ThirdYear,
            [35, 72, 91, 70]
        ),
        new(
            "Hugo", "Garcia", 118,
            GradeLevel.SecondYear,
            [92, 90, 83, 78]
        ),
        new(
            "Sven", "Mortensen", 113,
            GradeLevel.FirstYear,
            [88, 94, 65, 91]
        ),
        new(
            "Claire", "O'Donnell", 112,
            GradeLevel.FourthYear,
            [75, 84, 91, 39]
        ),
        new(
            "Svetlana", "Omelchenko", 111,
            GradeLevel.SecondYear,
            [97, 92, 81, 60]
        ),
        new(
            "Lance", "Tucker", 119,
            GradeLevel.ThirdYear,
            [68, 79, 88, 92]
        ),
        new(
            "Michael", "Tucker", 122,
            GradeLevel.FirstYear,
            [94, 92, 91, 91]
        ),
        new(
            "Eugene", "Zabokritski", 121,
            GradeLevel.FourthYear,
            [96, 85, 91, 60]
        )
    ];
}

enum GradeLevel
{
    FirstYear = 1,
    SecondYear,
    ThirdYear,
    FourthYear
};

Пример

Следующий запрос возвращает список учащихся, набравших 90 баллов или больше на первом экзамене.

void QueryHighScores(int exam, int score)
{
    var highScores =
        from student in students
        where student.ExamScores[exam] > score
        select new
        {
            Name = student.FirstName,
            Score = student.ExamScores[exam]
        };

    foreach (var item in highScores)
    {
        Console.WriteLine($"{item.Name,-15}{item.Score}");
    }
}

QueryHighScores(0, 90);

Этот запрос намеренно сделан простым, чтобы с ним можно было экспериментировать. Например, можно добавить в предложение where дополнительные условия или отсортировать результаты с помощью предложения orderby.

Классификация стандартных операторов запросов по способу выполнения

В реализации LINQ to Objects выполнение методов стандартных операторов запросов бывает немедленным и отложенным. Операторы запросов, использующие отложенное выполнение, можно дополнительно разделить на две категории: потоковые и непотоковые. Если вы знаете, каким образом выполняются разные операторы запросов, это может помочь понять результаты, полученные из данного запроса. Это особенно справедливо при изменении источника данных или создании одного запроса поверх другого. В этом разделе представлена классификация стандартных операторов запросов по способу выполнения.

Интерпретация

Немедленное выполнение означает, что источник данных считывается и операция выполняется один раз. Все стандартные операторы запросов, возвращающие скалярный результат, выполняются немедленно. Вы можете принудительно выполнить запрос немедленно с помощью Enumerable.ToList или Enumerable.ToArray методов. Немедленное выполнение обеспечивает повторное использование результатов запроса, а не объявление запроса. Результаты извлекаются один раз, а затем хранятся для дальнейшего использования.

Действие отложено

Отложенное выполнение означает, что операция не выполняется в той точке кода, где объявлен запрос. Она выполняется только после перечисления переменной запроса, например с помощью оператора foreach. Это означает, что результаты выполнения запроса зависят от содержимого источника данных при выполнении запроса, а не при его определении. Если переменная запроса перечисляется несколько раз, результаты могут каждый раз отличаться. Практически все стандартные операторы запроса, которые возвращают значения типа IEnumerable<T> или IOrderedEnumerable<TElement>, выполняются отложенным способом. Отложенное выполнение обеспечивает повторное использование запроса, так как запрос получает обновленные данные из источника данных при каждом итерации результатов запроса.

Операторы запросов, использующие отложенное выполнение, можно дополнительно разделить на потоковые и непотоковые.

Потоковая передача

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

Не используют потоковую передачу.

Непотоковые операторы должны считать все исходные данные до создания итогового элемента. В эту категорию попадают операции сортировки и группировки. Во время выполнения непотоковые операторы запросов считывают все исходные данные, помещают их в структуру данных, выполняют операцию и создают итоговые элементы.

Таблица классификации

В следующей таблице приведена классификация всех методов стандартных операторов запросов по способу выполнения.

Примечание.

Если оператор помечен в двух столбцах, в операции участвуют две входные последовательности, и каждая последовательность вычисляется по-разному. В таких случаях первая последовательность в списке параметров всегда вычисляется отложенным потоковым способом.

Стандартный оператор запроса Возвращаемый тип Немедленное выполнение Отложенное выполнение потоковой передачи Отложенное выполнение без потоковой передачи
Aggregate TSource X
All Boolean X
Any Boolean X
AsEnumerable IEnumerable<T> X
Average Одно числовое значение X
Cast IEnumerable<T> X
Concat IEnumerable<T> X
Contains Boolean X
Count Int32 X
DefaultIfEmpty IEnumerable<T> X
Distinct IEnumerable<T> X
ElementAt TSource X
ElementAtOrDefault TSource? X
Empty IEnumerable<T> X
Except IEnumerable<T> X X
First TSource X
FirstOrDefault TSource? X
GroupBy IEnumerable<T> X
GroupJoin IEnumerable<T> X X
Intersect IEnumerable<T> X X
Join IEnumerable<T> X X
Last TSource X
LastOrDefault TSource? X
LongCount Int64 X
Max Одно числовое значение, TSourceили TResult? X
Min Одно числовое значение, TSourceили TResult? X
OfType IEnumerable<T> X
OrderBy IOrderedEnumerable<TElement> X
OrderByDescending IOrderedEnumerable<TElement> X
Range IEnumerable<T> X
Repeat IEnumerable<T> X
Reverse IEnumerable<T> X
Select IEnumerable<T> X
SelectMany IEnumerable<T> X
SequenceEqual Boolean X
Single TSource X
SingleOrDefault TSource? X
Skip IEnumerable<T> X
SkipWhile IEnumerable<T> X
Sum Одно числовое значение X
Take IEnumerable<T> X
TakeWhile IEnumerable<T> X
ThenBy IOrderedEnumerable<TElement> X
ThenByDescending IOrderedEnumerable<TElement> X
ToArray TSource[] массив X
ToDictionary Dictionary<TKey,TValue> X
ToList IList<T> X
ToLookup ILookup<TKey,TElement> X
Union IEnumerable<T> X
Where IEnumerable<T> X

См. также