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<T>. 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, Skip<TSource> y Reverse<TSource>.
Al extender la interfaz IEnumerable<T>, 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, Min y Max. Para crear su propio método de agregación, agregue un método de extensión a la interfaz IEnumerable<T>.
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<T>.
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<T, TResult> 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<T, TResult> 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<T> 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<T>. 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<T>, 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
Métodos de extensión (Guía de programación de C#)