Condividi tramite


Procedura: aggiungere metodi personalizzati per le query LINQ

È possibile estendere il set di metodi utilizzabili per le query LINQ aggiungendo metodi di estensione all'interfaccia IEnumerable. Oltre alla media standard o al numero massimo di operazioni, ad esempio, è possibile creare un metodo di aggregazione personalizzato per calcolare un solo valore da una sequenza di valori. È inoltre possibile creare un metodo che funzioni come filtro personalizzato o come trasformazione di dati specifici per una sequenza di valori e che restituisca una nuova sequenza. Esempi di tali metodi sono Distinct``1, Skip``1 e Reverse``1.

Quando si estende l'interfaccia IEnumerable, è possibile applicare i metodi personalizzati a qualsiasi raccolta enumerabile. Per ulteriori informazioni, vedere Metodi di estensione (Guida per programmatori C#) o Metodi di estensione (Visual Basic).

Aggiunta di un metodo di aggregazione

Un metodo di aggregazione calcola un solo valore da un set di valori. In LINQ sono disponibili diversi metodi di aggregazione, inclusi Average``1, Min``1 e Max``1. È possibile creare un metodo di aggregazione personalizzato aggiungendo un metodo di estensione all'interfaccia IEnumerable.

Nell'esempio di codice seguente viene illustrato come creare un metodo di estensione denominato Median per calcolare una mediana per una sequenza di numeri di 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);
        }
    }
}

Questo metodo di estensione viene chiamato per qualsiasi raccolta enumerabile nello stesso modo in cui si chiamano gli altri metodi di aggregazione dall'interfaccia IEnumerable.

Nota

In Visual Basic, è possibile utilizzare una chiamata al metodo o una sintassi di query standard per la clausola Aggregate o Group By.Per ulteriori informazioni, vedere Clausola Aggregate (Visual Basic) e Clausola Group By (Visual Basic).

Nell'esempio di codice seguente viene illustrato come utilizzare il metodo Median per una matrice di 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
*/

Overload di un metodo di aggregazione per accettare tipi diversi

È possibile eseguire l'overload del metodo di aggregazione in modo che accetti sequenze di tipi diversi. L'approccio standard è creare un overload per ogni tipo. Un altro approccio è creare un overload che accetterà un tipo generico e lo convertirà in un tipo specifico utilizzando un delegato. È anche possibile combinare entrambi gli approcci.

Per creare un overload per ogni tipo

È possibile creare un overload specifico per ogni tipo che si desidera supportare. Nell'esempio di codice riportato di seguito viene illustrato un overload del metodo Median per il 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();
}

È ora possibile chiamare gli overload Median per i tipi integer e double, come illustrato nel codice seguente:

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

Per creare un overload generico

È inoltre possibile creare un overload che accetta una sequenza di oggetti generici. Questo overload accetta un delegato come parametro e lo utilizza per convertire una sequenza di oggetti di un tipo generico in un tipo specifico.

Nel codice seguente viene illustrato un overload del metodo Median che accetta il delegato Func come parametro. Questo delegato accetta un oggetto di tipo generico T e restituisce un oggetto di 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();
}

È ora possibile chiamare il metodo Median per una sequenza di oggetti di qualsiasi tipo. Se il tipo non dispone del proprio overload del metodo, è necessario passare un parametro delegato. In Visual Basic e in C#, è possibile utilizzare un'espressione lambda a questo scopo. Inoltre, solo in Visual Basic, se si utilizza la clausola Aggregate o Group By invece della chiamata al metodo, è possibile passare qualsiasi valore o espressione nell'ambito di questa clausola.

Nel codice di esempio di codice seguente viene illustrato come chiamare il metodo Median per una matrice di valori interi e per una matrice di stringhe. Per le stringhe, viene calcolata la mediana per le lunghezze di stringhe nella matrice. Nell'esempio viene illustrato come passare il parametro delegato Func al metodo Median per ogni 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
*/

Aggiunta di un metodo che restituisce una raccolta

È possibile estendere l'interfaccia IEnumerable con un metodo della query personalizzata che restituisce una sequenza di valori. In questo caso, il metodo deve restituire una raccolta di tipo IEnumerable. Tali metodi possono essere utilizzati per applicare filtri o trasformazioni di dati a una sequenza di valori.

Nell'esempio seguente viene illustrato come creare un metodo di estensione denominato AlternateElements che restituisce ogni altro elemento in una raccolta, a partire dal primo 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;
}

È possibile chiamare questo metodo di estensione per qualsiasi raccolta enumerabile nello stesso modo in cui si chiamano gli altri metodi di aggregazione dall'interfaccia IEnumerable, come illustrato nel codice seguente:

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

Vedere anche

Riferimenti

IEnumerable

Metodi di estensione (Guida per programmatori C#)

Concetti

Metodi di estensione (Visual Basic)