共用方式為


寫你自己的 LINQ 擴充功能

所有 LINQ 型方法都遵循兩個類似的模式之一。 它們採用可列舉的序列。 它們會回傳不同的序列或單一值。 圖形的一致性可讓您藉由撰寫具有類似圖形的方法來擴充LINQ。 事實上,自 LINQ 第一次引進以來,.NET 連結庫在許多 .NET 版本中都獲得了新的方法。 在本文中,您會藉由撰寫遵循相同模式的自有方法,來查看擴充 LINQ 的範例。

新增 LINQ 查詢的自定義方法

透過在介面中加入擴充方法 IEnumerable<T> ,擴充你用於 LINQ 查詢的方法集。 例如,除了標準平均值或最大作業之外,您還可以建立自定義匯總方法,從值序列計算單一值。 您也會建立一個方法,作為一個用於數值序列的自定義篩選器或特定的數據轉換方法,並傳回新的序列。 這類方法的範例包括 DistinctSkipReverse

當您擴充 IEnumerable<T> 介面時,您可以將自定義方法套用至任何可列舉的集合。 如需詳細資訊,請參閱 延伸模組成員

匯總方法會從一組值計算單一值。 LINQ 提供數個匯總方法,包括 AverageMinMax。 將擴充方法新增至 IEnumerable<T> 介面,以建立你自己的聚合方法。

從 C# 14 開始,您可以宣告 延伸模組區塊 來包含多個擴充成員。 宣告一個擴充區塊,後面有關鍵字 extension ,後面是括號內的接收參數。 下列程式代碼範例示範如何在延伸模組區塊中建立稱為 Median 的擴充方法。 方法會計算類型 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];
        }
    }
}

您也可以將 this 修飾詞新增至靜態方法,以宣告 擴充方法。 下列程式代碼顯示對等 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];
        }
    }
}

呼叫任一可枚舉集合的擴充方法,就像從介面呼叫其他彙總方法 IEnumerable<T> 一樣。

下列程式代碼範例示範如何針對 型Median別 的陣列使用 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

超載你的彙總方法 ,使其接受各種類型的序列。 標準方法是為每個型別創建多載。 另一種方法是建立採用泛型型別的重載,並使用委派將其轉換為特定類型。 您也可以合併這兩種方法。

為你想支援的每種類型設計一個特定的超載。 下列程式碼範例顯示了Median型別的int方法多載。

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

現在,您可以呼叫 Median 的多載,適用於 integerdouble 類型,如下列程式代碼所示:

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

您也可以建立接受物件 泛型序列 的多載。 此多載方法接受委託作為參數,並用其將泛型型別的物件序列轉換為特定型別。

以下程式碼顯示對採用Median方法的Func<T,TResult>委託做為參數的重載。 此委派接受泛型型別 T 的物件,並傳回 型別 double的物件。

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

您現在可以針對任何類型的物件序列呼叫 Median 方法。 如果型別沒有自己的方法過載,就傳遞一個代理參數。 在 C# 中,您可以針對此目的使用 Lambda 表達式。 另外,只有在 Visual Basic 裡,如果你用 Aggregate or Group By 子句代替方法呼叫,你可以傳遞這個子句範圍內的任何值或表達式。

下列範例程式代碼示範如何呼叫 Median 整數陣列和字串陣列的方法。 針對字串,會計算陣列中字串長度的中位數。 此範例顯示如何將Func<T,TResult>委派參數傳遞給每個案例的Median方法。

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

擴充 IEnumerable<T> 介面,加入自訂查詢方法,回傳一 串數值。 在此情況下,方法必須傳回 類型的 IEnumerable<T>集合。 這類方法可用來將篩選或數據轉換套用至值序列。

下列範例示範如何建立名為 AlternateElements 的擴充方法,從第一個專案開始傳回集合中所有其他元素。

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

呼叫此擴充方法對任何可枚舉集合,就像從介面呼叫其他方法 IEnumerable<T> 一樣,如下程式碼所示:

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

本文所示的每個範例都有不同的 接收者。 這表示你必須在不同的擴充區塊中宣告每種方法,以指定唯一接收器。 下列程式代碼範例顯示具有三個不同擴充區塊的單一靜態類別,其中每一個區塊都包含本文中定義的其中一個方法:

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

最後一個延伸模組區塊會宣告泛型延伸模組區塊。 接收者的 type 參數是宣告在 extension 上。

上述範例會在每個延伸模組區塊中宣告一個擴充成員。 在大部分情況下,您會為同一個接收者建立多個擴充成員。 在這種情況下,應在單一擴充區塊中宣告這些成員的擴充功能。