ジェネリックの仕組みはクラスだけでなくメソッドやデリゲートでも利用される。それらを活用した新しいスタイルのC#&VBプログラミングを学ぶ。
前編ではジェネリック・クラスについて解説しました。ジェネリック・クラスは、特定の型に依存しない汎用的なクラスであり、インスタンス作成時にそのクラスで扱う具体的な型を、型パラメータにより指定します。
ジェネリックの仕組みは、クラス以外にもメソッドやデリゲートにも適用できます。後編である今回は、ジェネリックなメソッドである「ジェネリック・メソッド」から解説を始めます。
前編ではリスト構造を実装している「ArrayListクラス」と、そのジェネリック版である「Listジェネリック・クラス」を比較しました。ここでも同様に、通常のメソッドとジェネリック・メソッドを比較しながら解説を行っていきます。
ここで取り上げるメソッドはArrayクラス(System名前空間)のIndexOfメソッドです。Arrayクラスそのものがコード内に登場することは少ないかもしれませんが、C#やVBの配列の正体は、実はArrayクラスのインスタンスです。
配列を操作するためのいくつかのユーティリティ的なメソッドは、Arrayクラスの静的メソッドとして定義されています。IndexOfメソッドもその1つで、このメソッドは指定された値が、指定された配列の何番目に存在するかを調べるためのものです。
■ジェネリックでないIndexOfメソッド
まず、ジェネリックの仕組みを使用していない通常のIndexOfメソッドは次のようにして使用します。このバージョンのIndexOfメソッドは.NET Framework 1.xでも利用可能です(以降のサンプル・プログラムでは、上がC#、下がVB)。
using System;
class IndexOfSample {
static void Main() {
int result;
string[] myArray = {"これは", "文字列の", "配列です"};
result = Array.IndexOf(myArray, "文字列の"); // (1)result:1
result = Array.IndexOf(myArray, "数値の"); // (2)result:-1
result = Array.IndexOf(myArray, 123); // (3)result:-1
}
}
Imports System
Class IndexOfSample
Shared Sub Main()
Dim result As Integer
Dim myArray As String() = {"これは", "文字列の", "配列です"}
result = Array.IndexOf(myArray, "文字列の") ' (1)result:1
result = Array.IndexOf(myArray, "数値の") ' (2)result:-1
result = Array.IndexOf(myArray, 123) ' (3)result:-1
End Sub
End Class
IndexOfメソッドは、指定した値の要素が見つかればその順番を、見つからなければ-1を返します。
さて、このサンプル・プログラムでは(2)と(3)の行の結果はどちらも-1ですが、この2つは意味が少し異なります。(2)は「数値の」という文字列が見つからなかっただけです。しかし(3)では、変数myArrayは文字列の配列ですから、数値である123は見つかるはずがありません。これは、シャケがどのコーナーで売られているんだろうと八百屋さんを探しているようなものです。
このIndexOfメソッドの仕様(シグネチャ:戻り値の型とパラメータの個数および型)は次のようになっています。
int IndexOf (Array array, Object value)
Function IndexOf(array As Array, value As Object) As Integer
このメソッドは汎用的に、つまりどのような型の配列に対しても値を検索できるように、第2パラメータはObject型となっています。しかしこのために、文字列の配列で数値を探すようなことが起こり得ます。
■IndexOfジェネリック・メソッド
ジェネリック・メソッド(VBではジェネリック・プロシージャとも呼ばれます)は、ジェネリックの仕組みをメソッドに適用したもので、メソッドの呼び出し時に、そのメソッドで扱う型を型パラメータにより指定します。
.NET Framework 2.0では、Arrayクラスに次のようなIndexOfジェネリック・メソッドが追加されています。
int IndexOf<T>(T[] array, T value)
Function IndexOf(Of T)(array As T(), value As T) As Integer
このメソッドの呼び出し方は、ジェネリック・クラスでのインスタンス作成方法と同じで、メソッドの呼び出し時に型パラメータである「T」の部分に具体的な型を指定します。
このバージョンのIndexOfメソッドを使うと、先ほどのサンプル・プログラムの(1)〜(3)の部分は次のようになります。
result = Array.IndexOf<string>(myArray, "文字列の"); // (1)1
result = Array.IndexOf<string>(myArray, "数値の"); // (2)-1
result = Array.IndexOf<string>(myArray, 123); // (3)コンパイル・エラー
result = Array.IndexOf(Of String)(myArray, "文字列の") ' (1)1
result = Array.IndexOf(Of String)(myArray, "数値の") ' (2)-1
result = Array.IndexOf(Of String)(myArray, 123) ' (3)コンパイル・エラー
この場合、型パラメータにstring型(=文字列型)を指定しているので、メソッドの第1パラメータはstring型の配列、第2パラメータは文字列の値でなければなりません。よって(3)の呼び出しはコンパイル・エラーとなります。お門違いなミスを未然に防げるわけです。
なお、例えばListジェネリック・クラスにある次のようなシグネチャのIndexOfメソッドはジェネリック・メソッドではないので混同しないでください。
int IndexOf(T item)
Function IndexOf(item As T) As Integer
これはジェネリック・クラス内で定義されている普通のメソッドです*。メソッド名の直後で型パラメータを指定するのがジェネリック・メソッドです。
* Arrayクラスはジェネリックではない(ジェネリックである必要がない)ためジェネリックなIndexOfメソッドが必要となりますが、Listクラスはジェネリックであるため、ジェネリックなIndexOfメソッドは不要です。
【コラム】 ジェネリック・メソッドにおける型パラメータの推論
ジェネリック・メソッドではコンパイラが型パラメータを“推論”してくれるために、型パラメータの指定を省略できる場合があります。
例えば、Arrayクラスには新しく追加されたResizeジェネリック・メソッドがあります。このメソッドは配列のサイズを変更するためのものです*。
* といっても、実際には指定されたサイズの配列を新しく宣言し、そこに要素をコピーするだけのようです。
以下にResizeジェネリック・メソッドを使ったサンプル・プログラムを示します。
using System;
class ResizeSample {
static void Main() {
string[] myArray = {"これは", "文字列の", "配列です"};
Array.Resize<string>(ref myArray, 5);
Console.WriteLine(myArray.Length); // 出力:5
Array.Resize<string>(ref myArray, 2);
Console.WriteLine(myArray.Length); // 出力:2
}
}
Imports System
Class ResizeSample
Shared Sub Main()
Dim myArray As String() = {"これは", "文字列の", "配列です"}
Array.Resize(Of String)(myArray, 5)
Console.WriteLine(myArray.Length) ' 出力:5
Array.Resize(Of String)(myArray, 2)
Console.WriteLine(myArray.Length) ' 出力:2
End Sub
End Class
Resizeメソッドを呼び出すとき、第1パラメータに指定する配列の型と型パラメータTに指定する型は常に同じです。配列がstring型ならばTもstringだし、配列がint型ならTもintです。
このような場合には、型パラメータの指定を省略できます。つまり、上記のサンプル・プログラムは以下のような書き換えが可能です。
Array.Resize<string>(ref myArray, 5);
↓↓↓
Array.Resize(ref myArray, 5);
Array.Resize(Of String)(myArray, 5)
↓↓↓
Array.Resize(myArray, 5)
型パラメータを省略してしまうと、それがジェネリック・メソッドかどうかはコードを見ただけでは分かりません。
また、もしジェネリック・メソッドでない同名のメソッドが存在した場合には、そちらが使われます(ちょうどArrayクラスのIndexOfメソッドがこのパターンです)。このような場合にジェネリック・メソッドの方を呼び出すには、型パラメータを明示的に指定する必要があります。
これまでオーバーロードされたメソッドというのは、パラメータ(=実引数)の数や型が異なるものだけでしたが、型パラメータの導入により、オーバーロードのバリエーションが増えることになります。
【コラム】 defaultキーワードによる初期値の取得
ジェネリック・クラスやジェネリック・メソッドの導入により、さまざまな型を扱えるクラスやメソッドの記述が可能です。しかし例えばC#で変数の内容を初期化したいようなときには、型パラメータで指定された型(ここではTとします)に従って、Tが参照型ならnullを、数値型なら0を代入する必要があります。
このような場合には、C#ではdefaultキーワードを使って「default(T)」と記述すれば、Tの種類に合わせてその初期値を得ることができます。VBではNothingがその役目を果たします。
以下のサンプル・プログラムは、string型(参照型)、int型(値型)、DateTime型(構造体)について、default(T)あるいはNothingがどのような値を返すかを表示します。
using System;
class DefaultValueTest {
// ジェネリック・メソッド
static T getDefaultValue<T>() {
T myVar = default(T);
return myVar;
}
static void Main() {
// 参照型の場合
string s = getDefaultValue<string>();
Console.WriteLine(s); // 出力:(何もなし)
// 値型の場合
int i = getDefaultValue<int>();
Console.WriteLine(i); // 出力:0
// 構造体の場合
DateTime dt = getDefaultValue<DateTime>();
Console.WriteLine(dt); // 出力:0001/01/01 0:00:00
}
}
Imports System
Class DefaultValueTest
' ジェネリック・メソッド
Shared Function getDefaultValue(Of T)() As T
Dim myVar As T = Nothing
Return myVar
End Function
Shared Sub Main
' 参照型の場合
Dim s As String = getDefaultValue(Of String)()
Console.WriteLine(s) ' 出力:(何もなし)
' 値型の場合
Dim i As Integer = getDefaultValue(Of Integer)()
Console.WriteLine(i) ' 出力:0
' 構造体の場合
Dim dt As DateTime = getDefaultValue(Of DateTime)()
Console.WriteLine(dt) ' 出力:0001/01/01 0:00:00
End Sub
End Class
構造体については、その各メンバが0やnull(VBではNothing)で初期化された値が返されます。
Copyright© Digital Advantage Corp. All Rights Reserved.