ジェネリックメソッドとは?―「型だけ違う同じ処理」を1つにまとめよう【C#入門】

広告

C#を学んでいると、こんな書き方を目にすることがあります。

Show<int>(10);
Show<string>("こんにちは");

そしてメソッド側を見ると、次のように書かれています。

static void Show<T>(T value)
{
    Console.WriteLine(value);
}

初めて見ると、次のような疑問がわいてくるかもしれません。

  • <T> って何?
  • なぜ普通のメソッドではダメなの?
  • T value とは何?
  • <int> や <string> は何を指定しているの?

この記事では、いきなり難しい定義から入らず、「型だけ違う、ほとんど同じメソッドが増えてきたらどうする?」というところから、ジェネリックメソッドを考えていきます。

この記事で分かること

  • ジェネリックメソッド Show<T>(T value) の読み方
  • <T> と T value の役割の違い
  • <int> を省略できる理由(型推論)
  • List<int> や Unity の GetComponent<T>() とのつながり
  • object を使う方法との違い
項目内容
対象C#を学び始めたばかりの方
前提メソッドの定義と呼び出し、引数・戻り値が分かる
実行環境コンソールアプリ(.NET)で動作確認できます

まず、普通のメソッドを確認しよう

たとえば、整数を表示するメソッドを作ってみます。

static void Show(int value)
{
    Console.WriteLine(value);
}

呼び出すときは Show(10); です。引数 value には int 型の値が入ります。ここは特に難しくありません。

string も表示したくなった

今度は文字列も表示したくなったとします。

static void Show(string value)
{
    Console.WriteLine(value);
}

これで Show("こんにちは"); と書けます。ここで、2つのメソッドを並べて比べてみましょう。

static void Show(int value)
{
    Console.WriteLine(value);
}

static void Show(string value)
{
    Console.WriteLine(value);
}

違うのは int と string の部分だけです。中身の処理 Console.WriteLine(value); はまったく同じです。

型だけ違うメソッドが増えていく

さらに double も表示したくなると、次のようになります。

static void Show(int value)
{
    Console.WriteLine(value);
}

static void Show(string value)
{
    Console.WriteLine(value);
}

static void Show(double value)
{
    Console.WriteLine(value);
}

もちろん、これはC#として間違いではありません。引数の型が違う同じ名前のメソッドを作る方法は、オーバーロードと呼ばれます。

しかし今回は、どのメソッドも処理がまったく同じで、違うのは型だけです。そこで、次のように考えます。

「型の部分も、変数のように“あとから入れる”ことはできないだろうか?」

それを実現するのが、ジェネリックメソッドです。

ジェネリックメソッドにしてみよう

先ほどの3つのメソッドを、1つにまとめてみます。

static void Show<T>(T value)
{
    Console.WriteLine(value);
}

見慣れない <T> と T value が出てきました。ここでの T は、あとから決める型につけた仮の名前だと考えてください。

イラスト:型だけ違う3つのShowメソッドを、ジェネリックメソッドShow<T>(T value)の1つにまとめる様子と、Tに型が入るイメージ

T は「型を入れる箱」のようなもの

普通の変数は、int number = 10; のように、number という名前の場所に値を入れます。

ジェネリックでは少し考え方が違います。Show<T> の T には、値ではなく型が入ります。

呼び出し方T に入る型イメージ上のメソッド
Show<int>(10);intstatic void Show(int value)
Show<string>("こんにちは");stringstatic void Show(string value)
Show<double>(3.14);doublestatic void Show(double value)

呼び出すときに < > の中へ型を書くと、その型が T の場所にはまる、というイメージです。

<T> と T value は役割が違う

ここは最初につまずきやすいところです。static void Show<T>(T value) には T が2回出てきますが、役割が違います。

Show<T>
     ↑
     「このメソッドでは、型をあとから決めます」という宣言

T value
↑
     決まった型を、引数 value の型として使います
書いてある場所役割
Show<T>型をあとから決める、という宣言
T value宣言した T を、引数の型として使う

実際に動かしてみよう

次のコードを試してみましょう。

using System;

class Program
{
    static void Show<T>(T value)
    {
        Console.WriteLine(value);
    }

    static void Main()
    {
        Show<int>(100);
        Show<string>("こんにちは");
        Show<double>(3.14);
    }
}

実行結果は次のとおりです。

100
こんにちは
3.14

1つのメソッドで、int・string・double を扱えました。

<int> は省略できることが多い

実は、先ほどのコードは次のように書いても動きます。

Show(100);
Show("こんにちは");
Show(3.14);

C#が、渡された値から T の型を判断してくれるからです。この仕組みを型推論といいます。

呼び出し渡した値C#の判断
Show(100);100 は intT は int
Show("こんにちは");"こんにちは" は stringT は string
Show(3.14);3.14 は doubleT は double

そのため実際のコードでは、Show<int>(100); よりも Show(100); と書かれることが多くあります。

イラスト:Show<T>(T value)の<T>は型をあとから決める宣言、T valueはその型を使う場所であることと、型推論でTが決まる流れ

戻り値にも T を使える

T は引数だけでなく、戻り値の型にも使えます。

static T GetValue<T>(T value)
{
    return value;
}

使う側は次のようになります。

int number = GetValue(10);
string message = GetValue("こんにちは");

10 を渡せば int として返り、文字列を渡せば string として返ります。

2つの値を入れ替える例

ジェネリックメソッドらしさが分かりやすい例として、2つの値を入れ替えるメソッドを作ってみます。

static void Swap<T>(ref T a, ref T b)
{
    T temp = a;   // a を一時的に退避
    a = b;
    b = temp;
}

int で使う

int x = 10;
int y = 20;

Swap(ref x, ref y);

Console.WriteLine(x);
Console.WriteLine(y);
20
10

string で使う

string a = "Apple";
string b = "Banana";

Swap(ref a, ref b);

Console.WriteLine(a);
Console.WriteLine(b);
Banana
Apple

ジェネリックを使わなければ、Swap(int...)・Swap(string...)・Swap(double...) のように、型ごとに似たメソッドを作ることになります。ジェネリックなら Swap<T> の1つで済みます。

List<int> で見たことがあるかもしれない

ジェネリックという言葉を知らなくても、すでに使ったことがある人は多いと思います。

List<int> numbers = new List<int>();
List<string> names = new List<string>();

<int> は「このListでは int を扱います」という意味です。今回学んでいるジェネリックメソッドも、基本的な考え方は同じです。

書き方あとから決めるもの
List<T>クラスで扱う型
Show<T>()メソッドで扱う型

Unity でもジェネリックメソッドは出てくる

Unityを使っていると、こんなコードを見かけます。

Rigidbody rb = GetComponent<Rigidbody>();
Transform t = GetComponent<Transform>();

GetComponent<T>() もジェネリックメソッドです。「どの種類のComponentが欲しいですか?」という問いに対して、< > の中で型を指定しています。

呼び出しT に入る型意味
GetComponent<Rigidbody>()RigidbodyこのGameObjectから Rigidbody を探して取得する
GetComponent<Transform>()TransformこのGameObjectから Transform を探して取得する

つまり GetComponent<T>() という1つの仕組みで、いろいろな種類のComponentを取得できるようになっています。Unityで突然出てくるように見える < > も、C#のジェネリックを知ると意味が見えやすくなります。

GetComponent<Transform>() と transform の違いは、こちらの記事で詳しく解説しています。
Unityの transform.Rotate() と GetComponent<Transform>().Rotate() は何が違う?

なぜ object ではダメなの?

ここまで読んで、「object を使えば何でも受け取れるのでは?」と思った人もいるかもしれません。

static void Show(object value)
{
    Console.WriteLine(value);
}

Show(10);
Show("こんにちは");

確かに、表示するだけならこれでも動きます。しかしジェネリックには、元の型を保ったまま処理できるという大きな利点があります。

static T GetValue<T>(T value)
{
    return value;
}

int number = GetValue(10);   // 戻り値が int だと分かっている

object で受け取って返すと、戻り値はただの object になり、int として使うにはキャスト(型変換)が必要になります。

static object GetValueObj(object value)
{
    return value;
}

int number = (int)GetValueObj(10);   // キャストが必要

ジェネリックは「何でも扱えるようにしながら、型の情報もできるだけ失わない」ための仕組み

T という名前でなければダメ?

いいえ。T でなければならないわけではありません。

static void Show<TValue>(TValue value)
{
    Console.WriteLine(value);
}

ただし、単純なジェネリックでは T(Type の頭文字)がよく使われます。複数の型を扱う場合は、TKey・TValue・TItem のように意味の分かる名前をつけることもあります。

すべてをジェネリックにすればいいわけではない

ジェネリックは便利ですが、何でもジェネリックにすればよいわけではありません。

static int Add(int a, int b)
{
    return a + b;
}

このように「int を扱うメソッドだ」とはっきり決まっているなら、無理にジェネリックにする必要はありません。ジェネリックが役に立つのは、処理は同じなのに、扱う型だけが違うという場面です。

まずはこの3つを覚えよう

最初からジェネリックのすべてを理解する必要はありません。まずは次の3つで十分です。

① <T> は「型をあとから決める」

static void Show<T>(T value)

T は、あとから決める型の仮の名前です。

② 同じ処理をいろいろな型で使える

Show(10);
Show("こんにちは");
Show(3.14);

③ Unity でも使われている

GetComponent<Rigidbody>();

ジェネリックはC#だけの難しい文法として覚えるのではなく、Unityで普段使っているコードを理解するための知識として見ると、意味がつかみやすくなります。

まとめ

普通のメソッドジェネリックメソッド
書き方static void Show(int value)static void Show<T>(T value)
扱う型あらかじめ決まっている呼び出すときに決まる
型ごとのメソッド型の数だけ必要1つで済む
  • <T> は「型をあとから決める」という宣言、T value はその型を引数に使う書き方
  • Show(100); のように、型は渡した値から推論されることが多い
  • List<int> や GetComponent<T>() も同じ考え方
  • object と違い、元の型の情報を保ったまま扱える

ジェネリックメソッドを初めて見たときは、<T> という記号に目が行きがちです。しかし、本当に大事なのは記号そのものではありません。

処理は同じ。違うのは型だけ。
なら、その型をあとから決められるようにしよう。

これが、ジェネリックメソッドを理解する最初の一歩です。

訪問数 7 回, 今日の訪問数 7回

広告