Cómo: Agregar métodos personalizados para las consultas LINQ

Para extender el conjunto de métodos que se puede usar en las consultas LINQ, agregue métodos de extensión a la interfaz IEnumerable. Por ejemplo, además de la media estándar o de las operaciones máximas, se puede crear un método de agregación personalizado para calcular un valor único de una secuencia de valores. También se puede crear un método que funcione como filtro personalizado o transformación de datos específica para una secuencia de valores y que devuelva una nueva secuencia. Algunos ejemplos de tales métodos son Distinct``1, Skip``1 y Reverse``1.

Al extender la interfaz IEnumerable, puede aplicar los métodos personalizados a cualquier colección enumerable. Para obtener más información, vea Métodos de extensión (Guía de programación de C#) o Métodos de extensión (Visual Basic).

Agregar un método de agregación

Un método de agregación calcula un valor único de un conjunto de valores. LINQ proporciona varios métodos de agregación, incluidos Average``1, Min``1 y Max``1. Para crear su propio método de agregación, agregue un método de extensión a la interfaz IEnumerable.

En el ejemplo de código siguiente se muestra cómo crear un método de extensión denominado Median para calcular el valor medio de una secuencia de números de tipo 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);
        }
    }
}

Este método de extensión para cualquier colección enumerable se llama de la misma forma que cualquier otro método de agregación desde la interfaz IEnumerable.

Nota

En Visual Basic, se puede usar una llamada de método o sintaxis de consulta estándar para la cláusula Aggregate o Group By.Para obtener más información, vea Aggregate (Cláusula, Visual Basic) y Group By (Cláusula, Visual Basic).

En el siguiente ejemplo de código se muestra cómo usar el método Median para una matriz de tipo 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
*/

Sobrecargar un método de agregación para aceptar varios tipos

Puede sobrecargar el método de agregación para que acepte secuencias de varios tipos. El enfoque estándar es crear una sobrecarga para cada tipo. Otro enfoque consiste en crear una sobrecarga que tomará un tipo genérico y lo convertirá en un tipo específico mediante un delegado. También pueden combinarse ambos enfoques.

Para crear una sobrecarga para cada tipo

Puede crear una sobrecarga específica para cada tipo que desea admitir. En el ejemplo de código siguiente se muestra una sobrecarga del método Median para el tipo 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();
}

Ahora se puede llamar a las sobrecargas Median para los tipos integer y double, como se muestra en el código siguiente:

        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
*/

Para crear una sobrecarga genérica

También se puede crear una sobrecarga que acepta una secuencia de objetos genéricos. Esta sobrecarga toma un delegado como parámetro y lo usa para convertir una secuencia de objetos de tipo genérico en un tipo específico.

En el código siguiente se muestra una sobrecarga del método Median que toma el delegado Func como parámetro. Este delegado toma un objeto de tipo genérico T y devuelve un objeto de tipo 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();
}

Ahora se puede llamar al método Median para una secuencia de objetos de cualquier tipo. Si el tipo no tiene su propia sobrecarga de método, tiene que pasar un parámetro delegado. En Visual Basic y C#, se puede usar una expresión lambda para esta finalidad. Además, solo en Visual Basic, si se usa la cláusula Aggregate o Group By en lugar de la llamada de método, puede pasar cualquier valor o expresión que se encuentre en el ámbito de esta cláusula.

En el código de ejemplo siguiente se muestra cómo llamar al método Median para una matriz de enteros y una matriz de cadenas. Para las cadenas, se calcula el valor medio de la longitud de las cadenas en la matriz. En el ejemplo se muestra cómo pasar el parámetro delegado Func al método Median para cada caso.

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
*/

Agregar un método que devuelve una colección

Puede extender la interfaz IEnumerable con un método de consulta personalizado que devuelve una secuencia de valores. En este caso, el método debe devolver una colección de tipo IEnumerable. Tales métodos se pueden usar para aplicar filtros o transformaciones de datos a una secuencia de valores.

En el ejemplo siguiente se muestra cómo crear un método de extensión denominado AlternateElements que devuelve los elementos alternos de una colección, empezando por el primer elemento.

' 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;
}

Puede llamar a este método de extensión para cualquier colección enumerable de la misma forma que se llama a otros métodos de la interfaz IEnumerable, como se muestra en el código siguiente:

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
*/

Vea también

Referencia

IEnumerable

Métodos de extensión (Guía de programación de C#)

Conceptos

Métodos de extensión (Visual Basic)