Freigeben über


Schreiben Eigener Erweiterungen in LINQ

Alle LINQ-basierten Methoden folgen einem von zwei ähnlichen Mustern. Sie nehmen eine aufzählbare Sequenz. Sie geben entweder eine andere Sequenz oder einen einzelnen Wert zurück. Durch die Einheitlichkeit der Struktur können Sie LINQ erweitern, indem Sie Methoden mit einer ähnlichen Struktur schreiben. Tatsächlich haben die .NET-Bibliotheken seit der ersten Einführung von LINQ neue Methoden in vielen .NET-Versionen erhalten. In diesem Artikel finden Sie Beispiele für die Erweiterung von LINQ, indem Sie eigene Methoden schreiben, die demselben Muster entsprechen.

Hinzufügen von benutzerdefinierten Methoden für LINQ-Abfragen

Erweitern Sie den Satz von Methoden, die Sie für LINQ-Abfragen verwenden, indem Sie der IEnumerable<T> Schnittstelle Erweiterungsmethoden hinzufügen. Zusätzlich zu den standarddurchschnittlichen oder maximalen Vorgängen erstellen Sie beispielsweise eine benutzerdefinierte Aggregatmethode, um einen einzelnen Wert aus einer Folge von Werten zu berechnen. Sie erstellen auch eine Methode, die als benutzerdefinierter Filter oder eine bestimmte Datentransformation für eine Abfolge von Werten funktioniert und eine neue Sequenz zurückgibt. Beispiele für solche Methoden sind Distinct, Skipund Reverse.

Wenn Sie die IEnumerable<T> Schnittstelle erweitern, können Sie Ihre benutzerdefinierten Methoden auf jede aufzählbare Auflistung anwenden. Weitere Informationen finden Sie unter Erweiterungsmitglieder.

Eine Aggregatmethode berechnet einen einzelnen Wert aus einer Gruppe von Werten. LINQ stellt verschiedene Aggregatmethoden bereit, einschließlich Average, Minund Max. Erstellen Sie eine eigene Aggregatmethode, indem Sie der IEnumerable<T> Schnittstelle eine Erweiterungsmethode hinzufügen.

Ab C# 14 können Sie einen Erweiterungsblock deklarieren, um mehrere Erweiterungsmitglieder zu enthalten. Deklarieren Sie einen Erweiterungsblock mit dem Schlüsselwort extension gefolgt vom Empfängerparameter in Klammern. Das folgende Codebeispiel zeigt, wie eine Erweiterungsmethode Median in einem Erweiterungsblock erstellt wird. Die Methode berechnet einen Median für eine Sequenz von Zahlen vom Typ 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];
        }
    }
}

Sie können den this Modifizierer auch einer statischen Methode hinzufügen, um eine Erweiterungsmethode zu deklarieren. Der folgende Code zeigt die entsprechende Median Erweiterungsmethode:

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

Rufen Sie beide Erweiterungsmethoden für jede aufzählbare Auflistung auf die gleiche Weise auf, wie Sie andere Aggregatmethoden von der IEnumerable<T> Schnittstelle aufrufen.

Das folgende Codebeispiel zeigt, wie die Median Methode für ein Array vom Typ doubleverwendet wird.

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

Überladen Sie die Aggregatmethode so, dass sie Sequenzen verschiedener Typen akzeptiert. Der Standardansatz besteht darin, für jeden Typ eine Überladung zu erstellen. Ein anderer Ansatz ist das Erstellen einer Überladung, die einen generischen Typ annimmt und diesen mit einem Delegaten in einen bestimmten Typ konvertiert. Sie können auch beide Ansätze kombinieren.

Erstellen Sie eine bestimmte Überladung für jeden Typ, den Sie unterstützen möchten. Das folgende Codebeispiel zeigt eine Überladung der Median Methode für den int Typ.

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

Sie können jetzt die Median-Überladungen für beide(n) integer und double-Typen aufrufen, wie im folgenden Code gezeigt:

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

Sie können auch eine Überladung erstellen, die eine generische Abfolge von Objekten akzeptiert. Diese Überladung verwendet einen Delegaten als Parameter und verwendet ihn, um eine Abfolge von Objekten eines generischen Typs in einen bestimmten Typ zu konvertieren.

Der folgende Code zeigt eine Überladung der Median Methode, die den Func<T,TResult> Delegaten als Parameter verwendet. Dieser Delegat akzeptiert ein Objekt vom generischen Typ T und gibt ein Objekt vom Typ zurück double.

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

Sie können die Median Methode jetzt für eine Abfolge von Objekten eines beliebigen Typs aufrufen. Wenn der Typ nicht über eine eigene Methodenüberladung verfügt, übergeben Sie einen Delegatparameter. In C# können Sie zu diesem Zweck einen Lambda-Ausdruck verwenden. Außerdem können Sie in Visual Basic, wenn Sie die Aggregate- oder Group By-Klausel anstelle des Methodenaufrufs verwenden, einen beliebigen Wert oder Ausdruck übergeben, der sich im Bereich dieser Klausel befindet.

Im folgenden Beispielcode wird gezeigt, wie die Median Methode für ein Array ganzzahliger Zahlen und ein Array von Zeichenfolgen aufgerufen wird. Bei Zeichenfolgen wird der Median für die Längen von Zeichenfolgen im Array berechnet. Das Beispiel zeigt, wie der Func<T,TResult> Delegatparameter für jeden Fall an die Median Methode übergeben wird.

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

Erweitern Sie die IEnumerable<T> Schnittstelle mit einer benutzerdefinierten Abfragemethode, die eine Abfolge von Werten zurückgibt. In diesem Fall muss die Methode eine Sammlung vom Typ IEnumerable<T>zurückgeben. Solche Methoden können verwendet werden, um Filter oder Datentransformationen auf eine Abfolge von Werten anzuwenden.

Das folgende Beispiel zeigt, wie Sie eine Erweiterungsmethode AlternateElements erstellen, die jedes andere Element in einer Auflistung zurückgibt, beginnend mit dem ersten Element.

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

Rufen Sie diese Erweiterungsmethode für jede aufzählbare Auflistung genauso auf, wie Sie andere Methoden aus der IEnumerable<T> Schnittstelle aufrufen würden, wie im folgenden Code gezeigt:

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

Jedes In diesem Artikel gezeigte Beispiel weist einen anderen Empfänger auf. Dies bedeutet, dass Sie jede Methode in einem anderen Erweiterungsblock deklarieren müssen, der den eindeutigen Empfänger angibt. Das folgende Codebeispiel zeigt eine einzelne statische Klasse mit drei verschiedenen Erweiterungsblöcken, die jeweils eine der in diesem Artikel definierten Methoden enthalten:

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

Der letzte Erweiterungsblock deklariert einen generischen Erweiterungsblock. Der Typparameter für den Empfänger wird direkt auf extension deklariert.

Im vorherigen Beispiel wird ein Erweiterungselement in jedem Erweiterungsblock deklariert. In den meisten Fällen erstellen Sie mehrere Erweiterungsmitglieder für denselben Empfänger. Deklarieren Sie in diesen Fällen die Erweiterungen für diese Mitglieder in einem einzigen Erweiterungsblock.