【C#】ジェネリックについて

C#

 

C#のジェネリックについて最近勉強したので、理解と記録のために少しだけ書いていきます。

ジェネリックについて

 

私の「ジェネリック」についてのおおざっぱな認識を書いておきます。

 

ジェネリックは、任意の型を受け付けるTという特殊な型をつかうことにより、任意の型で動くメソッドやクラスを作れる便利機能。

使い時はもちろん、型だけ違って機能が同じメソッドやクラスを作るときです。

 

  • ジェネリッククラス

 

インスタンス化した際の<>に入る型でTにはいる型が決まります。

 

例えば、ジェネリック型のクラスpublic Myclass<T>というクラスを作った場合は、

 

var p = new Myclass<int>();

とインスタンス化することによって、クラス定義時に<>に入っていたTにintが対応することが決定される。そして、そのクラス内のあるTはpを使ってアクセスする場合は常にint型として扱われることになる。

 

  • ジェネリックメソッド

 

呼び出す際の<>に入る型で決まります。

例えば、public T  Sample(T a, T b) というジェネリックメソッドを作った場合は、

 

Show<int>(a, b);

として呼び出すことで、Tにintを反映させた public int Sample (int a, int b)として合う買うことが出きます。

 

または、引数の型で自動的に決めることもできて、

Show(1, 2);

とすれば、暗黙的にint型の引数であることがわかり、勝手にTにはintを反映してくれます。

 

しかし、暗黙的に消えめてもらう場合は、メソッドが型を推論できる情報を少なくとも1つは用意することが必要。例えば、メソッド内でT型が戻り値returnにしか現れないと、メソッド側で型の推論ができないので、その場合は、呼び出し元の引数なり<int>なりで明示してあげる必要がある。

 

つまり、なんでも受け付ける万能T型だと思ったが厳密にいうと違うということです。

 

確かにT型自体は何でも受け付けますが、それらを利用したメソッドなどがあらゆる型で使えるわけではないので、なにを入れてもいいわけでもないし、なにを入れても動くわけでもありません。

 

例えば T a; とした時、T型の変数aです。

 

これにはTをstringにすれば、a = “hello”として文字列のように扱うこともできるし、intと指定すればa = 1として数値のように扱うこともできます。あくまでT型のa自体は。

 

それを使ったジェネリックメソッド a.SubString()というメソッドがあったらどうでしょうか?

 

string型のaなら問題なく動きますが、int型のaだとこれは当然動きません。int型はSubStringメソッドを持っていないからです。

 

このように、T型自体には何でも入れられても、それらを使ったメソッドなどはTによって使えるものと使えないものに分かれてしまうということです。

 

そんなときのためにwhere T :で型の明示的な制限ができるらしいです。

 

例えばwhere T : ICompareable<T>
なら、ICompareableインターフェースを持った型、つまり、string型やInt32などでないと使うことができないことになり、制約に違反する型ではインスタンス化もできません。

 

もちろん制約をしなくても、メソッドやクラス内で使えない型がある場合は、VSコード記述の最中にエラーが出ますが、明示的に制約を設けることで、他の人が見て「どのような型で使うことができるメソッドあるいはクラスなのか」が明示的に教えてあげられるようになります。

 

例えばICompareableを制約に持っているなら「string型やint型しかTに入れちゃいけないんだな」と気が付けます。

 

つまり「保守性に関しても優れている」ともいえるでしょう。

 

制約はそれぞれの複数あるT型に、別々につけることができ、複数の制約を一度につけることもできます。

 

ジェネリックメソッドは、ジェネリッククラスの中出も宣言可能ですが、この時、ジェネリック型として同じものを使った場合、コンパイル時に「型パラメーター ‘T’ は、外の型からの型パラメーター ‘Bb<T>’ と同じ名前です」というような警告が出ます↓

 

 

これだと、クラスBbをインスタンス化した際、Huメソッドの方のT型も呼び出したわけでもないのに勝手に決められてしまいます。

 

また、ジェネリック型を使った変数は共変できません。共変とは、object[] list = new string[10]のように何でもありのオブジェクト型(基底型)にstring型(派生型)を代入できちゃうこと)

 

T型が決まったら、常に入れられる方は不変ということですね。

 

こう思うと、今までなんとなく使っていたList<int>とかって、Listの機能を持ったT型(任意の型)にintをあてはめて使っていたのか」と勝手になっとくしました。

 

T型はなんでも入れられる型と聞いて喜んでいましたが、けっこう制限や注意点が多いようで面倒ですね。まだ全然使いこなせていないし勉強が必要ですが、使いこなせればかなり便利らしいので頑張ります。

 

コメント