Dela via


Så här utökar du LINQ

Alla LINQ-baserade metoder följer ett av två liknande mönster. De tar en uppräkningsbar sekvens. De returnerar antingen en annan sekvens eller ett enda värde. Tack vare formenhet kan du utöka LINQ genom att skriva metoder med en liknande struktur. I själva verket har .NET-biblioteken fått nya metoder i många .NET-versioner sedan LINQ först introducerades. I den här artikeln visas exempel på hur du utökar LINQ genom att skriva egna metoder som följer samma mönster.

Lägga till anpassade metoder för LINQ-frågor

Du utökar den uppsättning metoder som du använder för LINQ-frågor genom att lägga till tilläggsmetoder i IEnumerable<T> gränssnittet. Utöver standardåtgärderna för genomsnitt eller maximalt skapar du till exempel en anpassad aggregeringsmetod för att beräkna ett enda värde från en sekvens med värden. Du skapar också en metod som fungerar som ett anpassat filter eller en specifik datatransformering för en sekvens med värden och returnerar en ny sekvens. Exempel på sådana metoder är Distinct, Skipoch Reverse.

När du utökar IEnumerable<T> gränssnittet kan du använda dina anpassade metoder för alla uppräkningsbara samlingar. Mer information finns i Tilläggsmetoder.

En aggregeringsmetod beräknar ett enda värde från en uppsättning värden. LINQ innehåller flera aggregeringsmetoder, inklusive Average, Minoch Max. Du kan skapa en egen aggregeringsmetod genom att lägga till en tilläggsmetod i IEnumerable<T> gränssnittet.

Från och med C# 14 kan du deklarera ett tilläggsblock som ska innehålla flera tilläggsmedlemmar. Du deklarerar ett tilläggsblock med nyckelordet extension följt av mottagarparametern inom parenteser. Följande kodexempel visar hur du skapar en tilläggsmetod som heter Median i ett tilläggsblock. Metoden beräknar en median för en sekvens med tal av typen 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];
        }
    }
}

Du kan också lägga till modifieraren i this en statisk metod för att deklarera en tilläggsmetod. Följande kod visar motsvarande Median tilläggsmetod:

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

Du anropar någon av tilläggsmetoden för en uppräkningsbar samling på samma sätt som du anropar andra aggregeringsmetoder från IEnumerable<T> gränssnittet.

Följande kodexempel visar hur du använder Median metoden för en matris av typen 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

Du kan överbelasta din aggregeringsmetod så att den accepterar sekvenser av olika typer. Standardmetoden är att skapa en överladdning för varje typ. En annan metod är att skapa en överlagring som tar en generisk typ och konvertera den till en specifik typ med hjälp av en delegering. Du kan också kombinera båda metoderna.

Du kan skapa en specifik överbelastning för varje typ som du vill stödja. Följande kodexempel visar överladdning av Median-metoden för int-typen.

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

Nu kan du anropa överlagringarna Median för båda integer typerna, double som du ser i följande kod:

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

Du kan också skapa en överlagring som accepterar en allmän sekvens med objekt. Den här överlagringen tar en delegering som en parameter och använder denna för att konvertera en sekvens av objekt av allmän typ till en specifik typ.

Följande kod visar en överlagring av metoden Median som tar delegaten Func<T,TResult> som en parameter. Denna delegering tar ett objekt av generisk typ T och returnerar ett objekt av typen double.

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

Nu kan du anropa Median metoden för en sekvens av objekt av vilken typ som helst. Om typen inte har en egen metodöverlagring måste du skicka in en delegatparameter. I C# kan du använda ett lambda-uttryck för det här ändamålet. Om du använder Aggregate satsen eller Group By i stället för metodanropet i Visual Basic kan du också skicka valfritt värde eller uttryck som finns i omfånget för den här satsen.

Följande exempelkod visar hur du anropar Median metoden för en matris med heltal och en matris med strängar. För strängar beräknas medianen för längden på strängarna i matrisen. Exemplet visar hur du skickar Func<T,TResult> delegatparametern till Median metoden för varje ärende.

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

Du kan utöka IEnumerable<T> gränssnittet med en anpassad frågemetod som returnerar en sekvens med värden. I det här fallet måste metoden returnera en samling av typen IEnumerable<T>. Sådana metoder kan användas för att tillämpa filter eller datatransformering på en sekvens med värden.

I följande exempel visas hur du skapar en tilläggsmetod med namnet AlternateElements som returnerar alla andra element i en samling, med början från det första elementet.

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

Du kan anropa den här tilläggsmetoden för valfri uppräkningsbar samling precis som du anropar andra metoder från IEnumerable<T> gränssnittet, som du ser i följande kod:

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

Varje exempel som visas i den här artikeln har en annan mottagare. Det innebär att varje metod måste deklareras i ett annat tilläggsblock som anger den unika mottagaren. I följande kodexempel visas en enda statisk klass med tre olika tilläggsblock, som var och en innehåller en av metoderna som definieras i den här artikeln:

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

Det sista tilläggsblocket deklarerar ett allmänt tilläggsblock. Typparametern för mottagaren deklareras på extension.

I föregående exempel deklareras en tilläggsmedlem i varje tilläggsblock. I de flesta fall skapar du flera tilläggsmedlemmar för samma mottagare. I dessa fall bör du deklarera tilläggen för dessa medlemmar i ett enda tilläggsblock.