【C#】ジェネリックメソッドを使用する方法

C#

はじめに

こうちゃ
こんにちは、こうちゃです。
今回は、C#でジェネリックメソッド(テンプレート関数)を使用する方法 について説明していきます。
  • ジェネリックメソッドの使用方法
  •  ジェネリックメソッドを使用したい方
  • 同じ内容の処理なのに、型が違うために同じメソッドを何個も作らないといけないと悩んでいる方
こうちゃ
それでは、やっていきましょう!

ジェネリックメソッドとは?

皆さんは、以下のような経験はないでしょうか?
「処理内容は同じなのに、戻り値と引数の型が違うから、処理内容が同じメソッドを何個も作らなきゃいけない…」
/// <summary>
/// 加算関数(int型限定)
/// </summary>
/// <param name="x">int型数値1</param>
/// <param name="y">int型数値2</param>
/// <returns>int型の加算結果</returns>
static int Add(int x, int y)
{
    return x + y;
}

/// <summary>
/// 加算関数(float型限定)
/// </summary>
/// <param name="x">float型数値1</param>
/// <param name="y">float型数値2</param>
/// <returns>float型の加算結果</returns>
static float Add(float x, float y)
{
    return x + y;
}

実際に例を出すとこんな感じです。
どちらもメソッド名が「Add」であり、メソッドの中身としては引数で渡されたxとyを足しているだけです。
まったく同じ処理ですが、「int型」と「float型」で型が異なるため、別メソッドとして定義する必要があります。
例として「int型」と「float型」のみですが、これに「double型」や「short型」などのAddメソッドが必要となると、Addメソッドを何個も定義しなければいけなくなります。

このように、型違いだけど処理内容が同じという状況はプログラミングをやっているとよく遭遇します
こんなプログラマーの悩みを解決できるのがジェネリックメソッドです。
ジェネリックメソッドは、型が違っても処理内容が同じであれば共通化できる という特徴があります。

ジェネリックメソッドの作り方

さて、ジェネリックメソッドが何かわかったところで、実際にどのように使うのか見ていきましょう。
ジェネリックメソッドは以下のような形式で記述されます。

アクセス修飾子 戻り値の型 メソッド名<型引数>()
{
    
}

アクセス修飾子は「public」や「private」などです。
本記事では、コンソールアプリでプロジェクトを作成しているため「static」になっていますが、気にしないでください。
アクセス修飾子の次に通常のメソッドと同様、戻り値の型が来て、メソッド名がきます。
普段のメソッドなら、メソッド名の隣は引数がきますが、メソッド名の隣に「<型引数>」が来るのがジェネリックメソッドの特徴になります。
型引数には基本的にどんな文字を指定しても問題ないですが、一般的には「<T>」「<Type>」などがよく使用されます。
本記事では「<Type>」を使用していきます。

それでは、先ほどのAddメソッドをジェネリックメソッドで書き換えてみましょう。

/// <summary>
/// 加算関数(型制限なし)
/// </summary>
/// <typeparam name="Type"></typeparam>
/// <param name="x">Type型数値1</param>
/// <param name="y">Type型数値2</param>
/// <returns>Type型の加算結果</returns>
static Type Add<Type>(Type x, Type y)
{
    return x + y;
}

うんうん、良いですね。
これで「int型」にも「float型」にも対応しているAddメソッドができました!

と言いたいところですが、この書き方だと実はいけないのです。
この記事を見て、実際に手を動かしている方は気付いたかと思いますが、このコードはビルドエラーが発生します。

CS0019	演算子 '+' を 'Type' と 'Type' 型のオペランドに適用することはできません

ビルドすると、上記のようなエラーが発生してしまうのですね。
これが、ジェネリックメソッドで数値を扱う場合の落とし穴です。
このエラー、
「Type型同士の足し算はできないよ!」
ということを言っています。
さて、なぜなのでしょうか…?

ジェネリックメソッドの型制約

先ほど、「Type型同士の足し算はできない!」と怒られましたが、その理由を説明していきます。

理由はとても単純。
コンパイラは、このAddメソッドで定義されている「Type」という型の正体が全く分からない からです。

「何を言っているの?」と思った方もいるかもしれません。
ですが、冷静に考えれば、コンパイラはまともなことを言っていることがわかります。
今回定義した「Type」という型、我々は「int」や「float」として使用することを考えていますが、コンパイラはそんなことはわかりません。
だって、この「Type」が「int」や「float」で使われるよ!なんてこと、誰も明言していないですから。
それはコンパイラだって注意したくなっちゃいますよね。

さて、怒られた理由がわかったところで、どうすれば「Type」が「int」や「float」などの数値型であると認識できようになるか考えていきましょう。
「Type」がどんな型か判別できないのは、この「Type」を定義した我々が一切情報を与えていないからです。
つまり、「Type」が数値型だという情報を与えてあげれば、「足し算してよいよ!」と許可をしてくれます。
定義した型の情報を与える(定義した型の範囲を制限する)ことを 型制約 といい、ジェネリックメソッドを扱う上で重要になります。

では、型制約の記述方法を見ていきましょう。

アクセス修飾子 戻り値の型 メソッド名<型引数>()
    where 型引数 : 制約内容
{

}

上記のように、「メソッド名<型引数>()」の後ろに「where 型引数 : 制約内容」と記述します。
この制約内容については種類が多くて紹介しきれないので、気になる方は「c# ジェネリックメソッド 制約」などで調べてみてください。

それでは、Addメソッドを型制約ありバージョンで書き換えてみましょう。

/// <summary>
/// 加算関数(型制限なし)
/// </summary>
/// <typeparam name="Type">数値型</typeparam>
/// <param name="x">Type型数値1</param>
/// <param name="y">Type型数値2</param>
/// <returns>Type型の加算結果</returns>
static Type Add<Type>(Type x, Type y)
    where Type : INumber<Type>
{
    return x + y;
}

今回は、「Type」が数値型だよということを表すために「INumber」を制約内容としました。
(「INumber」を使用するには「using System.Numerics;」が必要になります)
これを追加することで、x + y が可能になりました!
ジェネリックメソッドの完成です!

ジェネリックメソッドの使い方

ジェネリックメソッドが作成できたところで、実際に使っていきましょう。

static void Main(string[] args)
{
    var num_int = Add(1, 2);              // int型の Add が自動的に呼び出される(引数から判断)
    var num_short = Add<short>(10, 20);   // short型の Add を明示的に呼び出し(<short>を入れることで指定)
    var num_double = Add(100.0, 200.0);   // double型の Add が自動的に呼び出される(引数から判断)
    var num_string = Add("123", "456");   // コンパイルエラー:Typeは数値型であり、文字列(string)型は対象外のため
}

num_intとnum_doubleに関しては、引数に渡された型から判定して、それぞれint型、double型のAddメソッドを自動的に呼び出しています。
num_shortについては、「Add<short>」と明示的にshort型を指定してAddメソッドを呼び出しています。
このように、型を指定しなければ引数から自動的に型を判定し、指定すればその型でメソッドが実行されます。
今回の例だと、基本的に引数に整数を渡すと自動的にint型と判定されるため、short型やlong型にしたい場合は明示的に型を指定するようにしましょう。
最後に、num_stringについてですが、今回Addメソッドで定義したType型は「INumber」を制約としており、数値型限定となっています。
そのため、引数にstring型を渡すと、「INumber」に反すると判定され、コンパイルエラーになります。

さいごに

今回は、ジェネリックメソッドについて説明しました。
今回紹介したものは初歩的な内容で、ジェネリックメソッドはもっと奥が深いです!
興味のある方はぜひ調べてみてください!
きっとプログラミングの幅が広がりますよ♪

同じくジェネリック系統で、「ジェネリッククラス」というものも存在します。
また別の記事で紹介したいと思います。
今回はここまで♪

こうちゃ
それではみなさま、お疲れ様でした!
楽しいプログラミングライフを!
最新情報をチェックしよう!