所有 LINQ 型方法都遵循兩個類似的模式之一。 它們採用可列舉的序列。 它們會回傳不同的序列或單一值。 圖形的一致性可讓您藉由撰寫具有類似圖形的方法來擴充LINQ。 事實上,自 LINQ 第一次引進以來,.NET 連結庫在許多 .NET 版本中都獲得了新的方法。 在本文中,您會藉由撰寫遵循相同模式的自有方法,來查看擴充 LINQ 的範例。
新增 LINQ 查詢的自定義方法
透過在介面中加入擴充方法 IEnumerable<T> ,擴充你用於 LINQ 查詢的方法集。 例如,除了標準平均值或最大作業之外,您還可以建立自定義匯總方法,從值序列計算單一值。 您也會建立一個方法,作為一個用於數值序列的自定義篩選器或特定的數據轉換方法,並傳回新的序列。 這類方法的範例包括 Distinct、 Skip和 Reverse。
當您擴充 IEnumerable<T> 介面時,您可以將自定義方法套用至任何可列舉的集合。 如需詳細資訊,請參閱 延伸模組成員。
匯總方法會從一組值計算單一值。 LINQ 提供數個匯總方法,包括 Average、 Min和 Max。 將擴充方法新增至 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 的多載,適用於 integer 和 double 類型,如下列程式代碼所示:
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 上。
上述範例會在每個延伸模組區塊中宣告一個擴充成員。 在大部分情況下,您會為同一個接收者建立多個擴充成員。 在這種情況下,應在單一擴充區塊中宣告這些成員的擴充功能。