Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Все методы на основе LINQ соответствуют одному из двух аналогичных шаблонов. Они принимают перечисляемую последовательность. Они возвращают другую последовательность или одно значение. Согласованность фигуры позволяет расширить LINQ путем написания методов с аналогичной фигурой. На самом деле библиотеки .NET получили новые методы во многих выпусках .NET, так как LINQ впервые появился. В этой статье приведены примеры расширения LINQ путем написания собственных методов, которые соответствуют тому же шаблону.
Добавление пользовательских методов для запросов LINQ
Вы расширяете набор методов, используемых для запросов LINQ, добавив методы расширения в IEnumerable<T> интерфейс. Например, помимо стандартных средних или максимальных операций, вы создадите пользовательский агрегатный метод для вычисления одного значения из последовательности значений. Вы также создаете метод, который работает в качестве пользовательского фильтра или определенного преобразования данных для последовательности значений и возвращает новую последовательность. Примерами таких методов являются Distinct, Skipи Reverse.
При расширении IEnumerable<T> интерфейса можно применить пользовательские методы к любой перечисленной коллекции. Дополнительные сведения см. в статье Методы расширения.
Агрегатный метод вычисляет одно значение из набора значений. LINQ предоставляет несколько агрегатных методов, в том числе Average, Minи Max. Вы можете создать собственный агрегатный метод, добавив метод расширения в IEnumerable<T> интерфейс.
Начиная с C# 14, можно объявить блок расширения для хранения нескольких членов расширения. Вы объявляете блок расширения с ключевым словом extension
, за которым следует параметр приемника в скобках. В следующем примере кода показано, как создать метод расширения с именем Median
в блоке расширения. Метод вычисляет медиану для последовательности чисел типа double
.
extension(IEnumerable<double>? source)
{
public double Median()
{
if (source is null || !source.Any())
{
throw new InvalidOperationException("Cannot compute median for a null or empty set.");
}
var sortedList =
source.OrderBy(number => number).ToList();
int itemIndex = sortedList.Count / 2;
if (sortedList.Count % 2 == 0)
{
// Even number of items.
return (sortedList[itemIndex] + sortedList[itemIndex - 1]) / 2;
}
else
{
// Odd number of items.
return sortedList[itemIndex];
}
}
}
Можно также добавить модификатор this
к статическому методу для объявления метода расширения. В следующем коде показан эквивалентный Median
метод расширения:
public static class EnumerableExtension
{
public static double Median(this IEnumerable<double>? source)
{
if (source is null || !source.Any())
{
throw new InvalidOperationException("Cannot compute median for a null or empty set.");
}
var sortedList =
source.OrderBy(number => number).ToList();
int itemIndex = sortedList.Count / 2;
if (sortedList.Count % 2 == 0)
{
// Even number of items.
return (sortedList[itemIndex] + sortedList[itemIndex - 1]) / 2;
}
else
{
// Odd number of items.
return sortedList[itemIndex];
}
}
}
Вы вызываете любой метод расширения для любой перечисленной коллекции так же, как и другие статистические методы из IEnumerable<T> интерфейса.
В следующем примере кода показано, как использовать Median
метод для массива типов double
.
double[] numbers = [1.9, 2, 8, 4, 5.7, 6, 7.2, 0];
var query = numbers.Median();
Console.WriteLine($"double: Median = {query}");
// This code produces the following output:
// double: Median = 4.85
Вы можете перегрузить агрегатный метод , чтобы он принимал последовательности различных типов. Стандартный подход заключается в создании перегрузки для каждого типа. Другой подход заключается в создании перегрузки, которая принимает универсальный тип и преобразует ее в конкретный тип с помощью делегата. Вы также можете объединить оба подхода.
Вы можете создать определенную перегрузку для каждого типа, который требуется поддерживать. В следующем примере кода показана перегрузка метода Median
для типа int
.
// int overload
public static double Median(this IEnumerable<int> source) =>
(from number in source select (double)number).Median();
Теперь можно вызвать Median
перегрузки для обоих типов integer
и double
, как показано в следующем коде.
double[] numbers1 = [1.9, 2, 8, 4, 5.7, 6, 7.2, 0];
var query1 = numbers1.Median();
Console.WriteLine($"double: Median = {query1}");
int[] numbers2 = [1, 2, 3, 4, 5];
var query2 = numbers2.Median();
Console.WriteLine($"int: Median = {query2}");
// This code produces the following output:
// double: Median = 4.85
// int: Median = 3
Вы также можете создать перегрузку, которая принимает универсальную последовательность объектов. Эта перегрузка принимает делегат в качестве параметра и использует его для преобразования последовательности объектов универсального типа в конкретный тип.
В следующем коде показана перегрузка метода Median
, который принимает делегат Func<T,TResult> в качестве параметра. Этот делегат принимает объект универсального типа T и возвращает объект типа double
.
// generic overload
public static double Median<T>(
this IEnumerable<T> numbers, Func<T, double> selector) =>
(from num in numbers select selector(num)).Median();
Теперь можно вызвать Median
метод для последовательности объектов любого типа. Если тип не имеет собственной перегрузки метода, необходимо передать параметр делегата. В C#для этой цели можно использовать лямбда-выражение. Кроме того, только в Visual Basic, если вместо вызова метода используется условие Aggregate
или Group By
, можно передать любое значение или выражение, которое находится в области видимости данного условия.
В следующем примере кода показано, как вызвать Median
метод для массива целых чисел и массив строк. Для строк вычисляется медиана длины строк в массиве. В примере показано, как передать параметр делегата Func<T,TResult> методу Median
для каждого случая.
int[] numbers3 = [1, 2, 3, 4, 5];
/*
You can use the num => num lambda expression as a parameter for the Median method
so that the compiler will implicitly convert its value to double.
If there is no implicit conversion, the compiler will display an error message.
*/
var query3 = numbers3.Median(num => num);
Console.WriteLine($"int: Median = {query3}");
string[] numbers4 = ["one", "two", "three", "four", "five"];
// With the generic overload, you can also use numeric properties of objects.
var query4 = numbers4.Median(str => str.Length);
Console.WriteLine($"string: Median = {query4}");
// This code produces the following output:
// int: Median = 3
// string: Median = 4
Интерфейс можно расширить IEnumerable<T> с помощью пользовательского метода запроса, который возвращает последовательность значений. В этом случае метод должен возвращать коллекцию типов IEnumerable<T>. Такие методы можно использовать для применения фильтров или преобразований данных к последовательности значений.
В следующем примере показано, как создать метод расширения с именем AlternateElements
, который возвращает каждый другой элемент в коллекции, начиная с первого элемента.
// Extension method for the IEnumerable<T> interface.
// The method returns every other element of a sequence.
public static IEnumerable<T> AlternateElements<T>(this IEnumerable<T> source)
{
int index = 0;
foreach (T element in source)
{
if (index % 2 == 0)
{
yield return element;
}
index++;
}
}
Этот метод расширения можно вызвать для любой перечисленной коллекции так же, как и для вызова других методов из IEnumerable<T> интерфейса, как показано в следующем коде:
string[] strings = ["a", "b", "c", "d", "e"];
var query5 = strings.AlternateElements();
foreach (var element in query5)
{
Console.WriteLine(element);
}
// This code produces the following output:
// a
// c
// e
Каждый пример, показанный в этой статье, имеет другой приемник. Это означает, что каждый метод должен быть объявлен в другом блоке расширения, который указывает уникальный приемник. В следующем примере кода показан один статический класс с тремя разными блоками расширений, каждый из которых содержит один из методов, определенных в этой статье:
public static class EnumerableExtension
{
extension(IEnumerable<double>? source)
{
public double Median()
{
if (source is null || !source.Any())
{
throw new InvalidOperationException("Cannot compute median for a null or empty set.");
}
var sortedList =
source.OrderBy(number => number).ToList();
int itemIndex = sortedList.Count / 2;
if (sortedList.Count % 2 == 0)
{
// Even number of items.
return (sortedList[itemIndex] + sortedList[itemIndex - 1]) / 2;
}
else
{
// Odd number of items.
return sortedList[itemIndex];
}
}
}
extension(IEnumerable<int> source)
{
public double Median() =>
(from number in source select (double)number).Median();
}
extension<T>(IEnumerable<T> source)
{
public double Median(Func<T, double> selector) =>
(from num in source select selector(num)).Median();
public IEnumerable<T> AlternateElements()
{
int index = 0;
foreach (T element in source)
{
if (index % 2 == 0)
{
yield return element;
}
index++;
}
}
}
}
Конечный блок расширения задает универсальный блок расширения. Параметр типа для приемника объявляется непосредственно в extension
.
В предыдущем примере объявляется один член расширения в каждом блоке расширения. В большинстве случаев вы создаёте несколько членов расширения для одного приемника. В этих случаях следует объявить расширения для этих членов в одном блоке расширения.