方法: LINQ クエリのカスタム メソッドを追加する
LINQ クエリに使用できるメソッド セットは、IEnumerable インターフェイスに拡張メソッドを追加することによって拡張できます。 たとえば、標準的な平均演算または最大演算に加えて、一連の値から単一の値を求めるカスタム集計メソッドを作成することができます。 値のシーケンスに対してカスタム フィルターまたは特定のデータ変換として機能し、新しいシーケンスを返すメソッドを作成することもできます。 このようなメソッドには、Distinct``1、Skip``1、Reverse``1 などがあります。
IEnumerable インターフェイスを拡張するとき、任意の列挙可能なコレクションに対して独自のカスタム メソッドを適用できます。 詳細については、「拡張メソッド (C# プログラミング ガイド)」または「拡張メソッド (Visual Basic)」を参照してください。
集計メソッドの追加
集計メソッドは、複数の値の集合から単一の値を計算するメソッドです。 LINQ には、Average``1、Min``1、Max``1 など、いくつかの集計メソッドが用意されています。 IEnumerable インターフェイスに拡張メソッドを追加することによって、独自の集計メソッドを作成できます。
double 型の数値のシーケンスから中央値を求める Median という拡張メソッドの作成方法を次のコード例に示します。
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 インターフェイスから他の集計メソッドを呼び出すのと同じように、任意の列挙可能なコレクションに対して呼び出すことができます。
注意
Visual Basic では、Aggregate 句または Group By 句に、メソッド呼び出し構文または標準クエリ構文のどちらかを使用することができます。詳細については、「Aggregate 句 (Visual Basic)」および「Group By 句 (Visual Basic)」を参照してください。
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)
...
' 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
*/
さまざまな型を受け取るための集計メソッドのオーバーロード
集計メソッドをオーバーロードすることによって、集計メソッドがさまざまな型のシーケンスを受け取るようにできます。 この場合、それぞれの型に対応するオーバーロードを作成するのが標準的なアプローチです。 もう 1 つのアプローチとして、ジェネリック型を受け取るオーバーロードを作成し、デリゲートを使って特定の型に変換する方法もあります。 この 2 つの方法を組み合わせることもできます。
型ごとのオーバーロードを作成するには
サポートする型ごとに固有のオーバーロードを作成できます。 Median メソッドの integer 型のオーバーロードを次のコード例に示します。
' 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 デリゲートをパラメーターとして受け取る 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# では、ラムダ式を使用できます。 また、メソッド呼び出しの代わりに Aggregate 句または Group By 句を使用する場合、句のスコープ内にある任意の値または式を渡すことができます (Visual Basic のみ)。
整数の配列と文字列の配列に対して Median メソッドを呼び出す方法を次のコード例に示します。 文字列の場合は、配列内の各文字列の長さの中央値が計算されます。 この例は、それぞれの配列について、Median メソッドに Func デリゲート パラメーターを渡す方法をが示されています。
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 インターフェイスは、値のシーケンスを返すカスタム クエリ メソッドで拡張することができます。 この場合、IEnumerable 型のコレクションを返すメソッドを使用する必要があります。 このようなメソッドを使用して、値のシーケンスにフィルターまたはデータ変換を適用することができます。
コレクションの 1 番目の要素から 1 つおきに要素を返す 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 インターフェイスから他のメソッドを呼び出すのとまったく同じです。
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
*/