Procedura per aggiungere metodi personalizzati per query LINQ (C#)

È possibile estendere il set di metodi da usare per le query LINQ aggiungendo metodi di estensione all'interfaccia IEnumerable<T>. Oltre alla media standard o a un numero massimo di operazioni, ad esempio, è possibile creare un metodo di aggregazione personalizzato per calcolare un singolo valore da una sequenza di valori. È anche possibile creare un metodo che funzioni come un filtro personalizzato o una trasformazione di dati specifica per una sequenza di valori che restituisca una nuova sequenza. Esempi di tali metodi sono Distinct, Skip e Reverse.

Quando si estende l'interfaccia IEnumerable<T>, è possibile applicare i metodi personalizzati a qualsiasi raccolta enumerabile. Per altre informazioni, vedere Metodi di estensione.

Aggiungere un metodo di aggregazione

Un metodo di aggregazione calcola un singolo valore da un set di valori. LINQ offre diversi metodi di aggregazione, tra cui Average, Min e Max. È possibile creare il proprio metodo di aggregazione aggiungendo un metodo di estensione all'interfaccia IEnumerable<T>.

L'esempio di codice seguente illustra come creare un metodo di estensione denominato Median per calcolare un valore mediano per una sequenza di numeri di tipo double.

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

Chiamare questo metodo di estensione per qualsiasi raccolta enumerabile nello stesso modo in cui si chiamano altri metodi di aggregazione dall'interfaccia IEnumerable<T>.

L'esempio di codice seguente illustra come usare il metodo Median di una matrice di tipo 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

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 consiste nel creare un overload per ogni tipo. Un altro approccio consiste nel creare un overload che accetti un tipo generico e lo converta in un tipo specifico tramite un delegato. È anche possibile combinare entrambi gli approcci.

Creare un overload per ogni tipo

È possibile creare un overload specifico per ogni tipo che si vuole supportare. Nell'esempio di codice seguente viene illustrato l'overload del metodo Median per il tipo int.

// int overload
public static double Median(this IEnumerable<int> source) =>
    (from number in source select (double)number).Median();

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

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

Creare un overload generico

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

Il codice seguente mostra un overload del metodo Median che accetta il delegato Func<T,TResult> come parametro. Questo delegato accetta un oggetto di tipo generico T e restituisce un oggetto di tipo double.

// generic overload
public static double Median<T>(
    this IEnumerable<T> numbers, Func<T, double> selector) =>
    (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 ha un proprio overload del metodo, è necessario passare un parametro del delegato. In C# è possibile usare un'espressione lambda a questo scopo. Solo in Visual Basic, se si usa la clausola Aggregate o Group By anziché la chiamata al metodo, è possibile passare qualsiasi valore o espressione che si trovi nell'ambito della clausola.

L'esempio di codice seguente illustra come chiamare il metodo Median per una matrice di numeri interi e una matrice di stringhe. Per le stringhe, viene calcolato il valore mediano della lunghezza delle stringhe nella matrice. L'esempio mostra come passare il parametro del delegato Func<T,TResult> al metodo Median per ogni caso.

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

Aggiungere un metodo che restituisce una sequenza

È possibile estendere l'interfaccia IEnumerable<T> con un metodo di query personalizzato che restituisce una sequenza di valori. In questo caso, il metodo deve restituire una raccolta di tipo IEnumerable<T>. Tali metodi possono essere usati per applicare filtri o trasformazioni di dati in una sequenza di valori.

Nell'esempio seguente viene illustrato come creare un metodo di estensione denominato AlternateElements che restituisce tutti gli altri elementi in una raccolta, a partire dal primo elemento.

// 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++;
    }
}

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

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

Vedi anche