Практическое руководство. Добавление настраиваемых методов для запросов LINQ

Набор методов, которые можно использовать для запросов LINQ, можно расширить путем добавления методов расширения в интерфейс IEnumerable<T>. Например, в дополнение к стандартным операциям вычисления среднего арифметического или максимального значения можно создать настраиваемый статистический метод для вычисления одного значения на основе последовательности значений. Кроме того, можно создать метод, который работал бы как настраиваемый фильтр или особое преобразование данных для последовательности данных, возвращая новую последовательность. Примерами таких методов являются Distinct, Skip<TSource> и Reverse<TSource>.

После расширения интерфейса IEnumerable<T> пользовательские методы можно применять к любой перечислимой коллекции. Дополнительные сведения см. в разделе Методы расширения (Руководство по программированию в C#) или Методы расширения (Visual Basic).

Добавление статистического метода

Статистический метод служит для вычисления одного значения на основе набора значений. LINQ предоставляет несколько статистических методов, включая Average, Min и Max. Свой собственный статистический метод можно создать путем добавления метода расширения в интерфейс IEnumerable<T>.

В следующем примере кода показано, как создать метод расширения, именуемый Median, для вычисления медианы последовательности чисел типа double.

Imports System.Runtime.CompilerServices

Module LINQExtension

    ' Extension method for the IEnumerable(of T) interface. 
    ' The method accepts only values of the Double type.
    <Extension()> 
    Function Median(ByVal source As IEnumerable(Of Double)) As Double
        If source.Count = 0 Then
            Throw New InvalidOperationException("Cannot compute median for an empty set.")
        End If

        Dim sortedSource = From number In source 
                           Order By number

        Dim itemIndex = sortedSource.Count \ 2

        If sortedSource.Count Mod 2 = 0 Then
            ' Even number of items in list.
            Return (sortedSource(itemIndex) + sortedSource(itemIndex - 1)) / 2
        Else
            ' Odd number of items in list.
            Return sortedSource(itemIndex)
        End If
    End Function
End Module
public static class LINQExtension
{
    public static double Median(this IEnumerable<double> source)
    {
        if (source.Count() == 0)
        {
            throw new InvalidOperationException("Cannot compute median for an empty set.");
        }

        var sortedList = from number in source
                         orderby number
                         select number;

        int itemIndex = (int)sortedList.Count() / 2;

        if (sortedList.Count() % 2 == 0)
        {
            // Even number of items.
            return (sortedList.ElementAt(itemIndex) + sortedList.ElementAt(itemIndex - 1)) / 2;
        }
        else
        {
            // Odd number of items.
            return sortedList.ElementAt(itemIndex);
        }
    }
}

Этот метод расширения вызывается для любой перечислимой коллекции так же, как и другие статистические методы из интерфейса IEnumerable<T>.

Примечание

В Visual Basic можно использовать вызов метода или стандартный синтаксис запроса для предложения Aggregate или Group By.Дополнительные сведения см. в разделах Предложение Aggregate (Visual Basic) и Предложение Group By (Visual Basic).

В следующем примере кода показано, как использовать метод Median для массива типа double.

        Dim numbers1() As Double = {1.9, 2, 8, 4, 5.7, 6, 7.2, 0}

        Dim query1 = Aggregate num In numbers1 Into Median()

        Console.WriteLine("Double: Median = " & query1)



...


        ' This code produces the following output:
        '
        ' Double: Median = 4.85

        double[] numbers1 = { 1.9, 2, 8, 4, 5.7, 6, 7.2, 0 };

        var query1 = numbers1.Median();

        Console.WriteLine("double: Median = " + query1);



...


/*
 This code produces the following output:

 Double: Median = 4.85
*/

Перегрузка статистического метода для принятия различных типов

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

Создание перегрузки для каждого типа

Для каждого типа, поддержку которого требуется обеспечить, можно создать особую перегрузку. В следующем примере кода показана перегрузка метода Median для типа integer.

' Integer overload

<Extension()> 
Function Median(ByVal source As IEnumerable(Of Integer)) As Double
    Return Aggregate num In source Select CDbl(num) Into med = Median()
End Function
//int overload

public static double Median(this IEnumerable<int> source)
{
    return (from num in source select (double)num).Median();
}

Теперь можно вызывать перегрузки Median и для типа integer, и для типа double, как показано в следующем коде.

        Dim numbers1() As Double = {1.9, 2, 8, 4, 5.7, 6, 7.2, 0}

        Dim query1 = Aggregate num In numbers1 Into Median()

        Console.WriteLine("Double: Median = " & query1)



...


        Dim numbers2() As Integer = {1, 2, 3, 4, 5}

        Dim query2 = Aggregate num In numbers2 Into Median()

        Console.WriteLine("Integer: Median = " & query2)



...


' This code produces the following output:
'
' Double: Median = 4.85
' Integer: Median = 3
        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
 Integer: Median = 3
*/

Создание универсальной перегрузки

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

В следующем образце кода показана перегрузка метода Median, которая принимает делегат Func<T, TResult> как параметр. Этот делегат принимает объект универсального типа T и возвращает объект типа double.

' Generic overload.

<Extension()> 
Function Median(Of T)(ByVal source As IEnumerable(Of T), 
                      ByVal selector As Func(Of T, Double)) As Double
    Return Aggregate num In source Select selector(num) Into med = Median()
End Function
// Generic overload.

public static double Median<T>(this IEnumerable<T> numbers,
                       Func<T, double> selector)
{
    return (from num in numbers select selector(num)).Median();
}

Теперь метод Median можно вызывать для последовательности объектов любого типа. Если типу не соответствует собственная перегрузка метода, следует передать параметр-делегат. В Visual Basic и C# для этой цели можно воспользоваться лямбда-выражением. Кроме того, в Visual Basic при использовании предложения Aggregate или Group By вместо вызова метода можно передать любое значение или предложение, находящееся в области видимости этого предложения.

В следующем примере кода показано, как можно вызывать метод Median для массива целых чисел и массива строк. Для строк вычисляется медиана длин строк в массиве. В примере показано, как передать параметр-делегат Func<T, TResult> методу Median на каждый случай.

Dim numbers3() As Integer = {1, 2, 3, 4, 5}

' You can use num 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.

Dim query3 = Aggregate num In numbers3 Into Median(num)

Console.WriteLine("Integer: Median = " & query3)

Dim numbers4() As String = {"one", "two", "three", "four", "five"}

' With the generic overload, you can also use numeric properties of objects.

Dim query4 = Aggregate str In numbers4 Into Median(str.Length)

Console.WriteLine("String: Median = " & query4)

' This code produces the following output:
'
' Integer: Median = 3
' String: Median = 4
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:

 Integer: Median = 3
 String: Median = 4
*/

Добавление метода, возвращающего коллекцию

Интерфейс IEnumerable<T> можно расширить с помощью пользовательского метода запроса, который бы возвращал последовательность значений. В этом случае метод должен возвращать коллекцию типа IEnumerable<T>. Такие методы можно использовать для применения фильтров или преобразований данных к последовательности значений.

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

' Extension method for the IEnumerable(of T) interface. 
' The method returns every other element of a sequence.

<Extension()> 
Function AlternateElements(Of T)(
    ByVal source As IEnumerable(Of T)
    ) As IEnumerable(Of T)

    Dim list As New List(Of T)
    Dim i = 0
    For Each element In source
        If (i Mod 2 = 0) Then
            list.Add(element)
        End If
        i = i + 1
    Next
    Return list
End Function
// 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)
{
    List<T> list = new List<T>();

    int i = 0;

    foreach (var element in source)
    {
        if (i % 2 == 0)
        {
            list.Add(element);
        }

        i++;
    }

    return list;
}

Этот метод расширения вызывается для любой перечислимой коллекции точно так же, как и другие методы из интерфейса IEnumerable<T>, как показано в следующем коде.

Dim strings() As String = {"a", "b", "c", "d", "e"}

Dim query = strings.AlternateElements()

For Each element In query
    Console.WriteLine(element)
Next

' This code produces the following output:
'
' a
' c
' e
string[] strings = { "a", "b", "c", "d", "e" };

var query = strings.AlternateElements();

foreach (var element in query)
{
    Console.WriteLine(element);
}
/*
 This code produces the following output:

 a
 c
 e
*/

См. также

Ссылки

IEnumerable<T>

Методы расширения (Руководство по программированию в C#)

Основные понятия

Методы расширения (Visual Basic)