Partage via


Écrire vos propres extensions dans LINQ

Toutes les méthodes LINQ suivent l’un des deux modèles similaires. Ils prennent une séquence énumérable. Ils retournent une séquence différente ou une valeur unique. La cohérence de la forme vous permet d’étendre LINQ en écrivant des méthodes avec une forme similaire. En fait, les bibliothèques .NET ont acquis de nouvelles méthodes dans de nombreuses versions de .NET depuis l’introduction de LINQ. Dans cet article, vous voyez des exemples d’extension LINQ en écrivant vos propres méthodes qui suivent le même modèle.

Ajouter des méthodes personnalisées pour les requêtes LINQ

Étendez l’ensemble de méthodes que vous utilisez pour les requêtes LINQ en ajoutant des méthodes d’extension à l’interface IEnumerable<T> . Par exemple, en plus des opérations moyennes ou maximales standard, vous créez une méthode d’agrégation personnalisée pour calculer une valeur unique à partir d’une séquence de valeurs. Vous créez également une méthode qui fonctionne comme un filtre personnalisé ou une transformation de données spécifique pour une séquence de valeurs et retourne une nouvelle séquence. Voici des exemples de ces méthodes : Distinct, Skipet Reverse.

Lorsque vous étendez l’interface IEnumerable<T> , vous pouvez appliquer vos méthodes personnalisées à n’importe quelle collection énumérable. Pour plus d’informations, consultez les membres de l’extension.

Une méthode d’agrégation calcule une valeur unique à partir d’un ensemble de valeurs. LINQ fournit plusieurs méthodes d’agrégation, notamment Average, Minet Max. Créez votre propre méthode d’agrégation en ajoutant une méthode d’extension à l’interface IEnumerable<T> .

À compter de C# 14, vous pouvez déclarer un bloc d’extension pour contenir plusieurs membres d’extension. Déclarez un bloc d’extension avec le mot clé extension suivi du paramètre récepteur entre parenthèses. L’exemple de code suivant montre comment créer une méthode d’extension appelée Median dans un bloc d’extension. La méthode calcule une médiane pour une séquence de nombres de type double.

extension(IEnumerable<double>? source)
{
    public double Median()
    {
        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];
        }
    }
}

Vous pouvez également ajouter le this modificateur à une méthode statique pour déclarer une méthode d’extension. Le code suivant montre la méthode d’extension équivalente Median :

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

Appelez l’une ou l’autre méthode d’extension pour toute collection énumérable de la même façon que vous appelez d’autres méthodes d’agrégation à partir de l’interface IEnumerable<T> .

L’exemple de code suivant montre comment utiliser la Median méthode pour un tableau de type 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

Surchargez votre méthode d’agrégation pour qu’elle accepte des séquences de différents types. L’approche standard consiste à créer une surcharge pour chaque type. Une autre approche consiste à créer une surcharge qui prend un type générique et à la convertir en un type spécifique à l’aide d’un délégué. Vous pouvez également combiner les deux approches.

Créez une surcharge spécifique pour chaque type que vous souhaitez prendre en charge. L’exemple de code suivant montre une surcharge de la Median méthode pour le int type.

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

Vous pouvez maintenant appeler les Median surcharges pour les deux integer types double , comme indiqué dans le code suivant :

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

Vous pouvez également créer une surcharge qui accepte une séquence générique d’objets. Cette surcharge prend un délégué comme paramètre et l’utilise pour convertir une séquence d’objets d’un type générique en un type spécifique.

Le code suivant montre une surcharge de la Median méthode qui prend le Func<T,TResult> délégué en tant que paramètre. Ce délégué prend un objet de type générique T et retourne un objet de type double.

// generic overload
public static double Median<T>(
    this IEnumerable<T> numbers, Func<T, double> selector) =>
    (from num in numbers select selector(num)).Median();

Vous pouvez maintenant appeler la Median méthode pour une séquence d’objets de n’importe quel type. Si le type n’a pas sa propre surcharge de méthode, transmettez un paramètre délégué. En C#, vous pouvez utiliser une expression lambda à cet effet. En outre, en Visual Basic uniquement, si vous utilisez la clause Aggregate ou Group By au lieu de l’appel de méthode, vous pouvez passer n’importe quelle valeur ou expression qui se trouve dans la portée de cette clause.

L’exemple de code suivant montre comment appeler la Median méthode pour un tableau d’entiers et un tableau de chaînes. Pour les chaînes, la médiane pour les longueurs de chaînes du tableau est calculée. L’exemple montre comment transmettre le Func<T,TResult> paramètre délégué à la Median méthode pour chaque cas.

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

Étendez l’interface IEnumerable<T> avec une méthode de requête personnalisée qui retourne une séquence de valeurs. Dans ce cas, la méthode doit retourner une collection de type IEnumerable<T>. Ces méthodes peuvent être utilisées pour appliquer des filtres ou des transformations de données à une séquence de valeurs.

L’exemple suivant montre comment créer une méthode d’extension nommée AlternateElements qui retourne tous les autres éléments d’une collection, à partir du premier élément.

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

Appelez cette méthode d’extension pour n’importe quelle collection énumérable comme vous le feriez pour appeler d’autres méthodes à partir de l’interface IEnumerable<T> , comme indiqué dans le code suivant :

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

Chaque exemple présenté dans cet article a un récepteur différent. Cela signifie que vous devez déclarer chaque méthode dans un bloc d’extension différent qui spécifie le récepteur unique. L’exemple de code suivant montre une classe statique unique avec trois blocs d’extension différents, chacun contenant l’une des méthodes définies dans cet article :

public static class EnumerableExtension
{
    extension(IEnumerable<double>? source)
    {
        public double Median()
        {
            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];
            }
        }
    }

    extension(IEnumerable<int> source)
    {
        public double Median() =>
            (from number in source select (double)number).Median();
    }

    extension<T>(IEnumerable<T> source)
    {
        public double Median(Func<T, double> selector) =>
            (from num in source select selector(num)).Median();

        public IEnumerable<T> AlternateElements()
        {
            int index = 0;
            foreach (T element in source)
            {
                if (index % 2 == 0)
                {
                    yield return element;
                }

                index++;
            }
        }
    }
}

Le bloc d’extension final déclare un bloc d’extension générique. Le paramètre de type du récepteur est déclaré sur lui-même extension .

L’exemple précédent déclare un membre d’extension dans chaque bloc d’extension. Dans la plupart des cas, vous créez plusieurs membres d’extension pour le même récepteur. Dans ces cas, déclarez les extensions pour ces membres dans un seul bloc d’extension.