Delen via


Uw eigen extensies schrijven naar LINQ

Alle op LINQ gebaseerde methoden volgen een van twee vergelijkbare patronen. Ze nemen een opsommingsvolgorde. Ze retourneren een andere reeks of één waarde. Met de consistentie van de shape kunt u LINQ uitbreiden door methoden te schrijven met een vergelijkbare vorm. In feite hebben de .NET-bibliotheken nieuwe methoden verkregen in veel .NET-releases sinds LINQ voor het eerst werd geïntroduceerd. In dit artikel ziet u voorbeelden van het uitbreiden van LINQ door uw eigen methoden te schrijven die hetzelfde patroon volgen.

Aangepaste methoden toevoegen voor LINQ-query's

Breid de set methoden die u gebruikt voor LINQ-query's uit door extensiemethoden toe te voegen aan de IEnumerable<T> interface. Naast de standaardgemiddelde of maximumbewerkingen maakt u bijvoorbeeld een aangepaste statistische methode om één waarde te berekenen op basis van een reeks waarden. U maakt ook een methode die werkt als een aangepast filter of een specifieke gegevenstransformatie voor een reeks waarden en retourneert een nieuwe reeks. Voorbeelden van dergelijke methoden zijn Distinct, Skipen Reverse.

Wanneer u de IEnumerable<T> interface uitbreidt, kunt u uw aangepaste methoden toepassen op elke enumerable verzameling. Zie Extensieleden voor meer informatie.

Met een statistische methode wordt één waarde berekend op basis van een set waarden. LINQ biedt verschillende statistische methoden, waaronder Average, Minen Max. Maak uw eigen statistische methode door een extensiemethode toe te voegen aan de IEnumerable<T> interface.

Vanaf C# 14 kunt u een extensieblok declareren om meerdere extensieleden te bevatten. Declareer een extensieblok met het trefwoord extension gevolgd door de ontvangerparameter tussen haakjes. In het volgende codevoorbeeld ziet u hoe u een extensiemethode maakt die wordt aangeroepen Median in een extensieblok. De methode berekent een mediaan voor een reeks getallen van het 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];
        }
    }
}

U kunt de this wijzigingsfunctie ook toevoegen aan een statische methode om een extensiemethode te declareren. De volgende code toont de equivalente Median extensiemethode:

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

Roep een van beide uitbreidingsmethodes aan voor elke opsommende verzameling op dezelfde manier als u andere statistische methoden aanroept vanuit de IEnumerable<T> interface.

In het volgende codevoorbeeld ziet u hoe u de Median methode gebruikt voor een matrix van het 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

Overbelast uw statistische methode zodat deze reeksen van verschillende typen accepteert. De standaardmethode is het creëren van een overbelasting voor elk type. Een andere benadering is het maken van een overbelasting die een algemeen type gebruikt en converteert naar een specifiek type met behulp van een gemachtigde. U kunt beide benaderingen ook combineren.

Maak een specifieke overbelasting voor elk type dat u wilt ondersteunen. In het volgende codevoorbeeld ziet u een overbelasting van de Median methode voor het int type.

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

U kunt nu de Median overbelastingen voor beide integer typen double aanroepen, zoals wordt weergegeven in de volgende code:

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

U kunt ook een overbelasting maken die een algemene reeks objecten accepteert. Deze overbelasting neemt een gemachtigde als parameter en gebruikt deze om een reeks objecten van een algemeen type te converteren naar een specifiek type.

De volgende code toont een overbelasting van de Median methode die de Func<T,TResult> gemachtigde als parameter gebruikt. Deze gemachtigde gebruikt een object van het algemene type T en retourneert een object van het 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();

U kunt nu de Median methode aanroepen voor een reeks objecten van elk type. Als het type geen eigen methodeoverbelasting heeft, geeft u een delegate-parameter door. In C# kunt u hiervoor een lambda-expressie gebruiken. Als u in Visual Basic alleen de Aggregate of Group By component gebruikt in plaats van de methode-aanroep, kunt u elke waarde of expressie doorgeven die binnen het bereik van deze component valt.

In de volgende voorbeeldcode ziet u hoe u de Median methode aanroept voor een matrix met gehele getallen en een matrix met tekenreeksen. Voor tekenreeksen wordt de mediaan voor de lengte van tekenreeksen in de matrix berekend. In het voorbeeld ziet u hoe u de Func<T,TResult> parameter voor gedelegeerden doorgeeft aan de Median methode voor elke case.

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

Breid de IEnumerable<T> interface uit met een aangepaste querymethode die een reeks waarden retourneert. In dit geval moet de methode een verzameling van het type IEnumerable<T>retourneren. Dergelijke methoden kunnen worden gebruikt om filters of gegevenstransformaties toe te passen op een reeks waarden.

In het volgende voorbeeld ziet u hoe u een extensiemethode maakt met de naam AlternateElements die elk ander element in een verzameling retourneert, te beginnen met het eerste 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++;
    }
}

Roep deze extensiemethode aan voor elke enumerable verzameling, net zoals u andere methoden vanuit de IEnumerable<T> interface zou aanroepen, zoals wordt weergegeven in de volgende code:

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

Elk voorbeeld dat in dit artikel wordt weergegeven, heeft een andere ontvanger. Dat betekent dat u elke methode in een ander extensieblok moet declareren waarmee de unieke ontvanger wordt opgegeven. In het volgende codevoorbeeld ziet u één statische klasse met drie verschillende extensieblokken, die elk een van de methoden bevat die in dit artikel zijn gedefinieerd:

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

Het uiteindelijke extensieblok declareert een algemeen extensieblok. De typeparameter voor de ontvanger wordt gedeclareerd op de extension zelf.

In het voorgaande voorbeeld wordt één extensielid in elk extensieblok declareren. In de meeste gevallen maakt u meerdere extensieleden voor dezelfde ontvanger. Declareer in die gevallen de extensies voor die leden in één extensieblok.