【C#】Genericsメソッド の使い方入門【Generics】

C#機能バージョン対応表作成しました。
こちらの記事も合わせてご覧ください。
※この記事には三項演算子が使用されています。
※Generics入門の記事になります。
最初にGenericsではないよく見るサンプルコードを表示します。
以下、サンプルコードになります。
サンプル1
1 2 3 4 5 6 7 8 |
public class GenericsProgram { public static int Max(int a, int b) { return a > b ? a : b; } } |
aとbを比較して、aが大きかった場合はaを返し、
そうでなければbを返します。
このメソッドにはint型の値以外を渡した場合は、もちろんエラーになります。
なので、double型で同じ処理のメソッドを利用したい場合は、
再度、型違いの類似メソッドを作成する必要があります。
それが、以下になります。
サンプル2
1 2 3 4 5 6 7 8 |
public class GenericsProgram { public static double Max(double a, double b) { return a > b ? a : b; } } |
う〜ん、ただ型が違うだけのメソッドですね。
同じクラスにこのような型違いの類似処理のメソッドを複数記述せずに
共通化するときに登場するのがGenericsメソッドです。
以下がGenericsメソッドになります。
サンプル3
1 2 3 4 5 6 7 8 |
public class GenericsProgram { static T Max<T>(T a, T b) where T : IComparable { return a.CompareTo(b) > 0 ? a : b; } } |
今回のポイントはこの【T】です。
Genericsメソッドとは型パラメーター【T】で宣言されるメソッドのことです。
このサンプル3を見た時にGenericsをご存知ない方はいくつか疑問が出るはずです。
- 【T】ってなに?
- where ってなに?
- 先ほどまでとメソッド内の処理が変わったのはなぜ?
この疑問点を1つずつ解決していきます。
【T】ってなに?
【T】とは型パラメーターのことで、int型やdouble型などの複数の型に対応するために一時的に宣言する型になります。
今回は【T】と宣言していますが、この名称は全ての宣言箇所で同じであればどんな名称でも問題ありません。
個人的には【T】、【Type】と宣言しているコードをよく目にします。(【T】の方が好きです。)
この説明だけではイメージしにくかもしれないので、
実際にGenericsメソッドの呼び出し方のサンプルコードを記載します。
1 2 3 4 5 6 7 8 9 |
int num1 = Max<int>(1, 2); // int型でメソッドを利用したい場合 double num2 = Max<double>(1, 2); // double型でメソッドを利用したい場合 int num3 = Max(1, 2); // 型を指定しない場合は渡した引数の型から判断される 今回はint型 double num4 = Max(1.0, 2.0); // 型を指定しない場合は渡した引数の型から判断される 今回はdouble型 // ちなみに数字以外でもいけます ↓↓ string str1 = Max<string>("abc", "def"); // この場合はアルファベット順で比較されます。 よって大きい方は【def】 string str1 = Max("abc", "def"); // string型の場合も型を指定しない場合は渡した引数の型から判断されます |
上記のようにメソッドの呼び出し方法を確認してからGenericsメソッドの宣言方法をもう一度見ると①〜④の【T】意味がわかりやすいと思います
1 2 |
T① Max<T②>(T③ a, T④ b) |
①どの型の値が返ってくるかわからないからとりあえずの【T】
②どの型で指定してメソッドを呼ばれるかわからないからとりあえずの【T】
③どの型が引数に渡されるかわからないからとりあえずの【T】
④どの型が引数に渡されるかわからないからとりあえずの【T】
ようするに何でもこい!とするための【T】です。
ここまでの説明でGenericsメソッドがプログラムの共通化をするのに便利そう!って気持ちになってくるはず。
では、次の説明に進みます。
where ってなに?
where とは制約条件を指定する際に使用します。
どんな型でも渡せてします【T】を制限するために利用します。
主に制約条件を指定することには理由は2つあります。
①つ目が、【T】がどんな型でも渡せてしまうと、どんな型でも処理が成立するような限定されたロジックしか定義できなくなるため。渡せる型を制限することで、限定的なロジック以外を定義するためです。(引数を10倍するメソッドを作りたいなら、文字型は渡せないように制限する必要がある)
②つ目が【T】はどんな型でも渡せる代わりに【T】そのままだとメソッドを呼び出すことができません。
しかし、【T】に制約条件をつけることで、その制約条件を満たしている型として【T】を扱うことができ、メソッドの呼び出しが可能になります。
今回の場合だと、【T】は IComparable インターフェースを継承している必要があるので、【T】の型である【a】や【b】からCompareToメソッドを呼び出すことができます。
先ほどまでとメソッド内の処理が変わったのはなぜ?
Genericsを使用する場合の約束事があり、使用する場合は比較などの演算子が利用できなくなります。
なので、以下のように処理を記載するとコンパイルエラーになります。
1 2 3 4 5 |
public static T Max<T>(T a, T b) { return a > b ? a : b; //【a > b】 ← この部分が比較演算子を利用しているためコンパイルエラーになる。 } |
じゃあどうしたら良いの?ってなるかと思いますが、以下の手順で考えながら同様の処理になるようプログラムを修正していくと大丈夫です。
①比較演算子が使えないから代わりに代用できるメソッドがないか考える ⇨ CompareToメソッドで代用する
②CompareToメソッドを使用するためには【T】にCompareToメソッドが使えるような制約条件を与えてあげる必要がある ⇨ where T : IComparable を定義する。
すると以下のようなサンプル3と同じプログラムになります。
1 2 3 4 5 6 7 8 9 |
public class GenericsProgram { static T Max<T>(T a, T b) where T : IComparable { return a.CompareTo(b) > 0 ? a : b; } } |
ここまでの説明でサンプルコード1をGenericsメソッドにするとサンプルコード3になる理由がわかったはずです。
これを気にGenericsメソッド化できそうな処理が身近にないか探してみてください。
制約条件一覧等はまた別の記事にしようと思います。