Заметка
Доступ к этой странице требует авторизации. Вы можете попробовать войти в систему или изменить каталог.
Доступ к этой странице требует авторизации. Вы можете попробовать сменить директорию.
Большинство запросов в документации по встроенному языку (LINQ) используют синтаксис декларативного запроса LINQ. Компилятор C# преобразует синтаксис запросов в вызовы методов. Эти вызовы метода реализуют стандартные операторы запросов. У них есть такие имена, как Where, Select, GroupBy, Join, Max и Average. Их можно вызывать непосредственно с помощью синтаксиса метода вместо синтаксиса запроса.
Синтаксис запросов и синтаксис методов семантически идентичны, но синтаксис запросов часто проще и проще читать. В качестве вызовов метода необходимо выразить некоторые запросы. Например, необходимо использовать вызов метода для выражения запроса, который возвращает число элементов, соответствующих указанным критериям. Вызов метода также необходимо использовать для запроса, который получает элемент с максимальным значением в исходной последовательности. В справочной документации по стандартным операторам запросов в пространствах имен System.Linq обычно применяется синтаксис методов. Узнайте, как использовать синтаксис метода в запросах и выражениях запросов.
Стандартные методы расширения оператора запросов
В следующем примере показано простое выражение запроса и семантически эквивалентный ему запрос, написанный как запрос, основанный на методе.
int[] numbers = [ 5, 10, 8, 3, 6, 12 ];
//Query syntax:
IEnumerable<int> numQuery1 =
from num in numbers
where num % 2 == 0
orderby num
select num;
//Method syntax:
IEnumerable<int> numQuery2 = numbers
.Where(num => num % 2 == 0)
.OrderBy(n => n);
foreach (int i in numQuery1)
{
Console.Write(i + " ");
}
Console.WriteLine(System.Environment.NewLine);
foreach (int i in numQuery2)
{
Console.Write(i + " ");
}
Оба примера дают одинаковый результат. Тип переменной запроса одинаков в обеих формах: IEnumerable<T>.
В правой части выражения обратите внимание, что where предложение теперь выражается как метод экземпляра объекта numbers , который имеет тип IEnumerable<int>. Если вы знакомы с универсальным IEnumerable<T> интерфейсом Where , вы знаете, что у него нет метода. Однако при вызове списка завершения IntelliSense в интегрированной среде разработки Visual Studio вы увидите не только Where метод, но и многие другие методы, такие как Select, SelectManyJoinи Orderby. Эти методы реализуют стандартные операторы запросов.
Хотя он выглядит так, как будто IEnumerable<T> включает в себя больше методов, это не так. Стандартные операторы запросов реализуются как методы расширения. Члены расширения "расширяют" существующий тип; их можно вызывать так, как если бы они были членами типа. Стандартные операторы запросов расширяют возможности IEnumerable<T>, поэтому вы можете написать numbers.Where(...). Вы вводите расширения в область видимости с помощью директив using перед их вызовом.
Для получения дополнительных сведений о членах расширения смотрите в разделе "Члены расширения". Дополнительные сведения о стандартных операторах запросов см. в разделе Общие сведения о стандартных операторах запроса (C#). Некоторые поставщики LINQ, такие как Entity Framework и LINQ to XML, реализуют собственные стандартные операторы запросов и члены расширения для других типов, кроме IEnumerable<T>.
Лямбда-выражения
В предыдущем примере условное выражение (num % 2 == 0) передается как встроенный аргумент методу Enumerable.Where: Where(num => num % 2 == 0). это выражение является лямбда-выражением. Это удобный способ написания кода, который в противном случае должен быть написан в более сложной форме.
num Слева от оператора — входная переменная, соответствующая num выражению запроса. Компилятор может вывести тип num, поскольку известно, что numbers является универсальным типом IEnumerable<T>. Текст лямбда-выражения совпадает с выражением в синтаксисе запроса или в любом другом выражении или операторе C#. Он может включать вызовы методов и другую сложную логику. Возвращаемое значение является результатом выражения. В синтаксисе метода можно выразить только определенные запросы, а некоторые из этих запросов требуют лямбда-выражений. Лямбда-выражения — это мощный и гибкий инструмент на панели элементов LINQ.
Возможность создания запросов
В приведенном выше примере кода метод Enumerable.OrderBy вызывается с помощью оператора dot в вызове Where.
Where создает отфильтрованную последовательность, а затем Orderby сортирует последовательность, созданную Where. Поскольку запросы возвращают IEnumerable, объедините их в синтаксис метода, собрав вызовы методов в цепочку. Компилятор выполняет эту композицию при написании запросов с помощью синтаксиса запросов. Так как переменная запроса не хранит результаты запроса, ее можно изменить или использовать в качестве основы для нового запроса в любое время, даже после его выполнения.
В следующих примерах показаны некоторые базовые запросы LINQ с помощью каждого подхода, указанного ранее.
Примечание.
Эти запросы работают с коллекциями в памяти; Однако синтаксис идентичен синтаксису, используемому в сущностях LINQ to Entities и LINQ to XML.
Пример — синтаксис запросов
Пишите большинство запросов с синтаксисом запроса для создания выражений запроса. В следующем примере показаны три выражения запросов. Первое выражение запроса показывает, как фильтровать или ограничивать результаты, применяя условия в предложении where. Оно возвращает все элементы исходной последовательности, значения которых больше 7 или меньше 3. Второе выражение показывает, как сортировать возвращаемые результаты. Третье выражение показывает, как группировать результаты по ключу. Этот запрос возвращает две группы на основе первой буквы слова.
List<int> numbers = [ 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 ];
// The query variables can also be implicitly typed by using var
// Query #1.
IEnumerable<int> filteringQuery =
from num in numbers
where num is < 3 or > 7
select num;
// Query #2.
IEnumerable<int> orderingQuery =
from num in numbers
where num is < 3 or > 7
orderby num ascending
select num;
// Query #3.
string[] groupingQuery = ["carrots", "cabbage", "broccoli", "beans", "barley"];
IEnumerable<IGrouping<char, string>> queryFoodGroups =
from item in groupingQuery
group item by item[0];
Тип запросов .IEnumerable<T> Все эти запросы можно написать, var как показано в следующем примере:
var query = from num in numbers...
В каждом предыдущем примере запросы фактически не выполняются, пока не выполняется итерацию по переменной запроса в foreach инструкции или другой инструкции.
Пример — синтаксис метода
В качестве вызова метода необходимо выразить некоторые операции запроса. Наиболее распространенными такими методами являются те методы, которые возвращают однотонные числовые значения, такие как Sum, Max, Minи Averageт. д. Вызывайте эти методы последними в любом запросе, потому что они возвращают одно значение и не могут быть использованы в качестве источника для дополнительных операций запроса. В следующем примере показан вызов метода в выражении запроса:
List<int> numbers1 = [ 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 ];
List<int> numbers2 = [ 15, 14, 11, 13, 19, 18, 16, 17, 12, 10 ];
// Query #4.
double average = numbers1.Average();
// Query #5.
IEnumerable<int> concatenationQuery = numbers1.Concat(numbers2);
Если метод имеет System.Action или System.Func<TResult> параметры, укажите эти аргументы в виде лямбда-выражения, как показано в следующем примере:
// Query #6.
IEnumerable<int> largeNumbersQuery = numbers2.Where(c => c > 15);
В предыдущих запросах выполняется только запрос #4, так как возвращает одно значение, а не универсальную IEnumerable<T> коллекцию. Сам метод использует foreach или аналогичный код для вычисления значения.
Вы можете написать каждый из предыдущих запросов с помощью неявного varввода, как показано в следующем примере:
// var is used for convenience in these queries
double average = numbers1.Average();
var concatenationQuery = numbers1.Concat(numbers2);
var largeNumbersQuery = numbers2.Where(c => c > 15);
Пример : синтаксис смешанного запроса и метода
В этом примере показано, как применять синтаксис метода к результатам предложения запроса. Заключите выражение запроса в скобки, а затем примените оператор dot и вызовите метод. В следующем примере запрос 7 возвращает количество чисел со значением от 3 до 7.
// Query #7.
// Using a query expression with method syntax
var numCount1 = (
from num in numbers1
where num is > 3 and < 7
select num
).Count();
// Better: Create a new variable to store
// the method call result
IEnumerable<int> numbersQuery =
from num in numbers1
where num is > 3 and < 7
select num;
var numCount2 = numbersQuery.Count();
Поскольку запрос 7 возвращает одно значение, а не коллекцию, он выполняется незамедлительно.
Предыдущий запрос можно написать с помощью неявного varввода следующим образом:
var numCount = (from num in numbers...
Его можно написать в синтаксисе метода следующим образом:
var numCount = numbers.Count(n => n is > 3 and < 7);
Его можно написать с помощью явного ввода, как показано ниже.
int numCount = numbers.Count(n => n is > 3 and < 7);
Динамическое определение фильтров предикатов во время выполнения
В некоторых случаях количество предикатов, которые нужно применить к исходным элементам в предложении where, неизвестно вплоть до времени выполнения. Одним из способов динамического определения сразу нескольких фильтров предикатов служит метод Contains, как показано в следующем примере. Запрос возвращает различные результаты в зависимости от значения id выполнения запроса.
int[] ids = [ 111, 114, 112 ];
var queryNames = from student in students
where ids.Contains(student.ID)
select new
{
student.LastName,
student.ID
};
foreach (var name in queryNames)
{
Console.WriteLine($"{name.LastName}: {name.ID}");
}
/* Output:
Garcia: 114
O'Donnell: 112
Omelchenko: 111
*/
// Change the ids.
ids = [ 122, 117, 120, 115 ];
// The query will now return different results
foreach (var name in queryNames)
{
Console.WriteLine($"{name.LastName}: {name.ID}");
}
/* Output:
Adams: 120
Feng: 117
Garcia: 115
Tucker: 122
*/
Примечание.
В этом примере используются следующие источник и данные:
record City(string Name, long Population);
record Country(string Name, double Area, long Population, List<City> Cities);
record Product(string Name, string Category);
static readonly City[] cities = [
new City("Tokyo", 37_833_000),
new City("Delhi", 30_290_000),
new City("Shanghai", 27_110_000),
new City("São Paulo", 22_043_000),
new City("Mumbai", 20_412_000),
new City("Beijing", 20_384_000),
new City("Cairo", 18_772_000),
new City("Dhaka", 17_598_000),
new City("Osaka", 19_281_000),
new City("New York-Newark", 18_604_000),
new City("Karachi", 16_094_000),
new City("Chongqing", 15_872_000),
new City("Istanbul", 15_029_000),
new City("Buenos Aires", 15_024_000),
new City("Kolkata", 14_850_000),
new City("Lagos", 14_368_000),
new City("Kinshasa", 14_342_000),
new City("Manila", 13_923_000),
new City("Rio de Janeiro", 13_374_000),
new City("Tianjin", 13_215_000)
];
static readonly Country[] countries = [
new Country ("Vatican City", 0.44, 526, [new City("Vatican City", 826)]),
new Country ("Monaco", 2.02, 38_000, [new City("Monte Carlo", 38_000)]),
new Country ("Nauru", 21, 10_900, [new City("Yaren", 1_100)]),
new Country ("Tuvalu", 26, 11_600, [new City("Funafuti", 6_200)]),
new Country ("San Marino", 61, 33_900, [new City("San Marino", 4_500)]),
new Country ("Liechtenstein", 160, 38_000, [new City("Vaduz", 5_200)]),
new Country ("Marshall Islands", 181, 58_000, [new City("Majuro", 28_000)]),
new Country ("Saint Kitts & Nevis", 261, 53_000, [new City("Basseterre", 13_000)])
];
Можно использовать инструкции потока управления, такие как if... else или switch, чтобы выбрать один из предопределенных альтернативных запросов. В следующем примере используется другое studentQuery предложение, where если значение oddYear времени выполнения равно true илиfalse.
void FilterByYearType(bool oddYear)
{
IEnumerable<Student> studentQuery = oddYear
? (from student in students
where student.Year is GradeLevel.FirstYear or GradeLevel.ThirdYear
select student)
: (from student in students
where student.Year is GradeLevel.SecondYear or GradeLevel.FourthYear
select student);
var descr = oddYear ? "odd" : "even";
Console.WriteLine($"The following students are at an {descr} year level:");
foreach (Student name in studentQuery)
{
Console.WriteLine($"{name.LastName}: {name.ID}");
}
}
FilterByYearType(true);
/* Output:
The following students are at an odd year level:
Fakhouri: 116
Feng: 117
Garcia: 115
Mortensen: 113
Tucker: 119
Tucker: 122
*/
FilterByYearType(false);
/* Output:
The following students are at an even year level:
Adams: 120
Garcia: 114
Garcia: 118
O'Donnell: 112
Omelchenko: 111
Zabokritski: 121
*/
Обработка значений NULL в выражениях запросов
В этом примере показано, как обрабатывать возможные нулевые значения в исходных коллекциях. Коллекция объектов, например IEnumerable<T>, может содержать элементы со значением NULL. Если исходная коллекция содержит элемент, значение которого равно nullnull, и запрос не обрабатывает null значения, NullReferenceException при выполнении запроса возникает исключение.
В следующем примере используются эти типы и статические массивы данных:
record Product(string Name, int CategoryID);
record Category(string Name, int ID);
static Category?[] categories =
[
new ("brass", 1),
null,
new ("winds", 2),
default,
new ("percussion", 3)
];
static Product?[] products =
[
new Product("Trumpet", 1),
new Product("Trombone", 1),
new Product("French Horn", 1),
null,
new Product("Clarinet", 2),
new Product("Flute", 2),
null,
new Product("Cymbal", 3),
new Product("Drum", 3)
];
Чтобы избежать исключения в связи с пустой ссылкой, можно составить защитный код, как показано в следующем примере:
var query1 = from c in categories
where c != null
join p in products on c.ID equals p?.CategoryID
select new
{
Category = c.Name,
Name = p.Name
};
В предыдущем примере предложение where отфильтровывает все пустые элементы в последовательности категорий. Этот метод не связан с проверкой на наличие пустых значений в предложении join. Условное выражение со значением NULL в этом примере работает, поскольку Products.CategoryID имеет тип int?, что является сокращением Nullable<int>.
Если в предложении join только один из ключей сравнения имеет тип, допускающий значение NULL, остальные типы в выражении запроса можно привести к типу, допускающему значение NULL. В следующем примере предполагается, что EmployeeID — это столбец, содержащий значения типа int?:
var query =
from o in db.Orders
join e in db.Employees
on o.EmployeeID equals (int?)e.EmployeeID
select new { o.OrderID, e.FirstName };
В каждом из примеров в запросе используется ключевое слово equals. Вы также можете использовать сопоставление шаблонов, включая шаблоны для is null и is not null. Эти шаблоны не рекомендуется использовать в запросах LINQ, так как поставщики запросов могут неправильно интерпретировать новый синтаксис C#. Поставщик запросов — это библиотека, которая преобразует выражения запросов C# в собственный формат данных, например Entity Framework Core. Поставщики запросов реализуют интерфейс System.Linq.IQueryProvider, чтобы создавать источники данных, реализующие интерфейс System.Linq.IQueryable<T>.
Обработка исключений в выражениях запросов
Можно вызвать любой метод в контексте выражения запроса. Не вызывайте какой-либо метод в выражении запроса, который может создать побочный эффект, например изменение содержимого источника данных или исключение. В этом примере показано, как избежать создания исключений при вызове методов в выражениях запросов без нарушения общих рекомендаций .NET относительно обработки исключений. Эти рекомендации позволяют поймать определенное исключение, когда вы понимаете, почему оно выбрасывается в заданном контексте. Дополнительные сведения см. в разделе Лучшие методики обработки исключений.
В последнем примере показано, как обрабатывать случаи, когда необходимо создать исключение во время выполнения запроса.
В следующем примере показано, как переместить код обработки исключений за пределы выражения запроса. Этот способ можно рефакторинговать только в том случае, если метод не зависит от локальных переменных запроса. Проще справиться с исключениями за пределами выражения запроса.
// A data source that is very likely to throw an exception!
IEnumerable<int> GetData() => throw new InvalidOperationException();
// DO THIS with a datasource that might
// throw an exception.
IEnumerable<int>? dataSource = null;
try
{
dataSource = GetData();
}
catch (InvalidOperationException)
{
Console.WriteLine("Invalid operation");
}
if (dataSource is not null)
{
// If we get here, it is safe to proceed.
var query = from i in dataSource
select i * i;
foreach (var i in query)
{
Console.WriteLine(i.ToString());
}
}
В блоке catch (InvalidOperationException) , приведенном в предыдущем примере, обработайте исключение (или не обрабатывайте) таким образом, как это подходит для приложения.
В некоторых случаях лучший ответ на исключение, которое возникает в запросе, может быть немедленное прекращение выполнения запроса. В следующем примере демонстрируется обработка исключений, которые могут быть созданы из тела запроса. Предположим, что SomeMethodThatMightThrow потенциально может вызвать исключение, которое требует остановки выполнения запроса.
Блок try заключает foreach цикл, а не сам запрос. Цикл foreach — это точка выполнения запроса. Исключения во время выполнения создаются при выполнении запроса. Таким образом, обработайте их в цикле foreach .
// Not very useful as a general purpose method.
string SomeMethodThatMightThrow(string s) =>
s[4] == 'C' ?
throw new InvalidOperationException() :
$"""C:\newFolder\{s}""";
// Data source.
string[] files = ["fileA.txt", "fileB.txt", "fileC.txt"];
// Demonstration query that throws.
var exceptionDemoQuery = from file in files
let n = SomeMethodThatMightThrow(file)
select n;
try
{
foreach (var item in exceptionDemoQuery)
{
Console.WriteLine($"Processing {item}");
}
}
catch (InvalidOperationException e)
{
Console.WriteLine(e.Message);
}
/* Output:
Processing C:\newFolder\fileA.txt
Processing C:\newFolder\fileB.txt
Operation is not valid due to the current state of the object.
*/
Перехватите любое исключение, которое вы можете вызвать, и выполните любую необходимую очистку в блоке finally.