HOW TO:新增 LINQ 查詢的自訂方法
您可以將擴充方法加入至 IEnumerable<T> 介面,來擴充一組可用於 LINQ 查詢的方法。 例如,除了標準的平均值或最大值作業之外,您還可以建立自訂的彙總方法,從值序列中計算出單一值。 您也可以建立方法,用來當做值序列的自訂篩選條件或特定資料轉換,並傳回新的序列。 這類方法的範例有 Distinct、Skip<TSource> 和 Reverse<TSource>。
當您擴充 IEnumerable<T> 介面時,可以將自訂方法套用至任何可列舉的集合。 如需詳細資訊,請參閱擴充方法 (C# 程式設計手冊) 或擴充方法 (Visual Basic)。
加入彙總方法
彙總方法會從一組值計算出單一值。 LINQ 提供數個彙總方法,包括 Average、Min 和 Max。 您可以將擴充方法加入至 IEnumerable<T> 介面,建立自己的彙總方法。
在下列程式碼範例中,會示範如何建立名為 Median 的擴充方法,計算型別為 double 之數值序列的中位數。
Imports System.Runtime.CompilerServices
Module LINQExtension
' Extension method for the IEnumerable(of T) interface.
' The method accepts only values of the Double type.
<Extension()>
Function Median(ByVal source As IEnumerable(Of Double)) As Double
If source.Count = 0 Then
Throw New InvalidOperationException("Cannot compute median for an empty set.")
End If
Dim sortedSource = From number In source
Order By number
Dim itemIndex = sortedSource.Count \ 2
If sortedSource.Count Mod 2 = 0 Then
' Even number of items in list.
Return (sortedSource(itemIndex) + sortedSource(itemIndex - 1)) / 2
Else
' Odd number of items in list.
Return sortedSource(itemIndex)
End If
End Function
End Module
public static class LINQExtension
{
public static double Median(this IEnumerable<double> source)
{
if (source.Count() == 0)
{
throw new InvalidOperationException("Cannot compute median for an empty set.");
}
var sortedList = from number in source
orderby number
select number;
int itemIndex = (int)sortedList.Count() / 2;
if (sortedList.Count() % 2 == 0)
{
// Even number of items.
return (sortedList.ElementAt(itemIndex) + sortedList.ElementAt(itemIndex - 1)) / 2;
}
else
{
// Odd number of items.
return sortedList.ElementAt(itemIndex);
}
}
}
您可以對任何可列舉的集合呼叫這個擴充方法,其方式與您從 IEnumerable<T> 介面呼叫其他彙總方法一樣。
注意事項 |
---|
在 Visual Basic 中,您可以使用 Aggregate 或 Group By 子句的方法呼叫或標準查詢語法。 如需詳細資訊,請參閱 Aggregate 子句 (Visual Basic) 和 Group By 子句 (Visual Basic)。 |
在下列程式碼範例中,會示範如何將 Median 方法用於型別為 double 的陣列。
Dim numbers1() As Double = {1.9, 2, 8, 4, 5.7, 6, 7.2, 0}
Dim query1 = Aggregate num In numbers1 Into Median()
Console.WriteLine("Double: Median = " & query1)
...
' This code produces the following output:
'
' Double: Median = 4.85
double[] numbers1 = { 1.9, 2, 8, 4, 5.7, 6, 7.2, 0 };
var query1 = numbers1.Median();
Console.WriteLine("double: Median = " + query1);
...
/*
This code produces the following output:
Double: Median = 4.85
*/
多載彙總方法以接受各種型別
您可以多載彙總方法,讓它接受各種型別的序列。 一般的方法是為每個型別建立多載, 而另一個方法則是建立可接受泛型型別的多載,再使用委派將此多載轉換為特定型別。 您也可以混合使用這兩種方法。
若要為每個型別建立多載
您可以為想要支援的每個型別建立特定多載。 下列程式碼範例示範 integer 型別之 Median 方法的多載。
' Integer overload
<Extension()>
Function Median(ByVal source As IEnumerable(Of Integer)) As Double
Return Aggregate num In source Select CDbl(num) Into med = Median()
End Function
//int overload
public static double Median(this IEnumerable<int> source)
{
return (from num in source select (double)num).Median();
}
您現在可以呼叫 integer 及 double 型別的 Median 多載,如下列程式碼所示:
Dim numbers1() As Double = {1.9, 2, 8, 4, 5.7, 6, 7.2, 0}
Dim query1 = Aggregate num In numbers1 Into Median()
Console.WriteLine("Double: Median = " & query1)
...
Dim numbers2() As Integer = {1, 2, 3, 4, 5}
Dim query2 = Aggregate num In numbers2 Into Median()
Console.WriteLine("Integer: Median = " & query2)
...
' This code produces the following output:
'
' Double: Median = 4.85
' Integer: Median = 3
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
Integer: Median = 3
*/
若要建立泛型多載
您也可以建立接受泛型物件序列的多載。 這個多載接受委派做為參數,並使用此委派將泛型型別物件序列轉換為特定型別。
下列程式碼示範接受 Func<T, TResult> 委派做為參數之 Median 方法的多載。 這個委派接受泛型型別 T 的物件,並傳回 double 型別的物件。
' Generic overload.
<Extension()>
Function Median(Of T)(ByVal source As IEnumerable(Of T),
ByVal selector As Func(Of T, Double)) As Double
Return Aggregate num In source Select selector(num) Into med = Median()
End Function
// Generic overload.
public static double Median<T>(this IEnumerable<T> numbers,
Func<T, double> selector)
{
return (from num in numbers select selector(num)).Median();
}
您現在可以對任何型別的物件序列呼叫 Median 方法。 如果型別本身沒有專屬方法多載,您就必須傳遞委派參數。 在 Visual Basic 和 C# 中,您可以使用 Lambda 運算式來達到這個目的。 此外,僅就 Visual Basic 而言,如果您使用 Aggregate 或 Group By 子句而非方法呼叫,就可以在這個子句的範圍內傳遞任何值或運算式。
在下列範例程式碼中,會示範如何針對整數陣列和字串陣列呼叫 Median 方法。 就字串的案例而言,程式碼會計算陣列中字串長度的中位數。 針對每個案例,範例會示範如何將 Func<T, TResult> 委派參數傳遞至 Median 方法。
Dim numbers3() As Integer = {1, 2, 3, 4, 5}
' You can use num 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.
Dim query3 = Aggregate num In numbers3 Into Median(num)
Console.WriteLine("Integer: Median = " & query3)
Dim numbers4() As String = {"one", "two", "three", "four", "five"}
' With the generic overload, you can also use numeric properties of objects.
Dim query4 = Aggregate str In numbers4 Into Median(str.Length)
Console.WriteLine("String: Median = " & query4)
' This code produces the following output:
'
' Integer: Median = 3
' String: Median = 4
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:
Integer: Median = 3
String: Median = 4
*/
加入傳回集合的方法
您可以使用會傳回值序列的自訂查詢方法來擴充 IEnumerable<T> 介面。 在這種情況下,方法必須傳回 IEnumerable<T> 型別的集合。 您可以使用這類方法,將篩選條件或資料轉換套用至值的序列。
下列範例示範如何建立名為 AlternateElements 的擴充方法,該方法會從集合中的第一個項目開始,每隔一個項目傳回項目。
' Extension method for the IEnumerable(of T) interface.
' The method returns every other element of a sequence.
<Extension()>
Function AlternateElements(Of T)(
ByVal source As IEnumerable(Of T)
) As IEnumerable(Of T)
Dim list As New List(Of T)
Dim i = 0
For Each element In source
If (i Mod 2 = 0) Then
list.Add(element)
End If
i = i + 1
Next
Return list
End Function
// 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)
{
List<T> list = new List<T>();
int i = 0;
foreach (var element in source)
{
if (i % 2 == 0)
{
list.Add(element);
}
i++;
}
return list;
}
您可以對任何可列舉的集合呼叫這個擴充方法,就像從 IEnumerable<T> 介面呼叫其他方法一樣,如下列程式碼所示:
Dim strings() As String = {"a", "b", "c", "d", "e"}
Dim query = strings.AlternateElements()
For Each element In query
Console.WriteLine(element)
Next
' This code produces the following output:
'
' a
' c
' e
string[] strings = { "a", "b", "c", "d", "e" };
var query = strings.AlternateElements();
foreach (var element in query)
{
Console.WriteLine(element);
}
/*
This code produces the following output:
a
c
e
*/