ひとりでのアプリ開発 - fineの備忘録 -

ひとりでアプリ開発をするなかで起こったことや学んだことを書き溜めていきます

C# - ジェネリック(generics)

初めに

 C# には、ジェネリックと呼ばれるさまざまな型に対応したクラスや関数を定義する機能があります。本記事では、ジェネリックについて説明します。

ジェネリック

ジェネリックgenerics)とは

 ジェネリックgenerics:総称性)はさまざまな型に対応するために、型をパラメータとして与えて、その型に対応したクラスや関数を生成する機能です。

※英語的には、ジェネリクスとする方が正しいですが、Microsoft のドキュメントでもジェネリックと訳されています。Java では、ジェネリクスと表現されています。

ジェネリックの例

 次のコードはジェネリックを使っています。

class GenericClass<T>
{
    private T data;

    public void SetData(T input)
    {
        data = input;
    }

    public T GetData()
    {
        return data;
    }
}

 GenericClass というジェネリッククラスを定義し、型パラメータを T としています。また、SetDate、GetData の引数の型を型パラメータである T としています。

 ジェネリックを利用するメリットは、同じコードを異なる型に対して再利用できる点です。いままでは、int 型と float 型では、型が異なるため同じ処理がしたい場合でも別の関数を用意する必要がありました。しかし、ジェネリックを使うことで、同じ機能を持つ関数やクラスを複数の型に対して実装する必要がなくなります。

//int型で使う場合
GenericClass<int> genericObject = new GenericClass<int>();
genericObject.SetData(10);
int value = genericObject.GetData();

//string型で使う場合
GenericExample<string> stringExample = new GenericExample<string>();
stringExample.SetData("Hello, Generics!");
string stringValue = stringExample.GetData();

 この例では、クラスをジェネリックにしていますが、関数をジェネリックにすることもできます。

ジェネリッククラス

 ジェネリッククラスは次のように定義できます。

ジェネリッククラスの定義の仕方)

class クラス名<型パラメータ>
{
      //省略
}

 クラス内の変数で、任意の型を格納したい変数は型パラメータを使うことで実現できます。

 このクラスを参照するには、new 演算子を使ってインスタンスを生成するときに、型パラメータの型を指定します。

ジェネリッククラスの参照の仕方)

ジェネリッククラス名<型> インスタンス名 = new ジェネリッククラス名<型>();

(例)

using System;

//ジェネリッククラスの定義, 型パラメータはT
public class GenericExample<T> 
{
    private T data;  //dataという変数を型パラメータにすることで, 任意の型を格納可能に

    public void SetData(T input)
    {
        data = input;
    }

    public T GetData()
    {
        return data;
    }
}

public class Program
{
    public static void Main()
    {
        GenericExample<int> intExample = new GenericExample<int>();  //型パラメータをintに
        intExample.SetData(10);  //dataに10を格納
        int intValue = intExample.GetData();
        Console.WriteLine("Integer value: " + intValue);

        GenericExample<string> stringExample = new GenericExample<string>(); //型パラメータをstringに
        stringExample.SetData("Hello, Generics!");  //dataにstringを格納
        string stringValue = stringExample.GetData();
        Console.WriteLine("String value: " + stringValue);
    }
}
ジェネリックメソッド

 関数をジェネリックにすることもできます。ジェネリックメソッドの定義もジェネリッククラスと同様、関数名の後に型パラメータを記述するだけで可能です。

(例:引数として受け取った2つの値を交換する関数)

using System;

public class GenericMethods
{
     //ジェネリックメソッドの定義
    public static void Swap<T>(ref T a, ref T b)  //型パラメータはT  
    {
        T temp = a;
        a = b;
        b = temp;
    }
}

public class Program
{
    public static void Main()
    {
        int x = 10;
        int y = 20;
        Console.WriteLine("Before swap: x = " + x + ", y = " + y);

        GenericMethods.Swap(ref x, ref y);
        Console.WriteLine("After swap: x = " + x + ", y = " + y);
    }
}
制約条件、where

 where を使うことで、型パラメータに対して特定の制約条件を課すことができます。これにより、ジェネリックなコードをより具体的な型に制約し、より安全かつ効果的なコードを記述することができます。

(例)
 2つの引数を比較するジェネリックメソッドを作る場合、CompareTo を使うことができる型に制約する必要があります。そのため、型パラメータに IComparable という制約条件をつけます。

using System;

public class GenericFunctions
{
    public static T Max<T>(T a, T b) where T : IComparable<T>  //制約条件
    {
        if (a.CompareTo(b) >= 0)
        {
            return a;
        }
        else
        {
            return b;
        }
    }
}

public class Program
{
    public static void Main()
    {
        int maxInt = GenericFunctions.Max(5, 10);
        Console.WriteLine("Max integer: " + maxInt);  //10

        double maxDouble = GenericFunctions.Max(3.14, 2.71);
        Console.WriteLine("Max double: " + maxDouble);  //3.14

        string maxString = GenericFunctions.Max("apple", "banana");
        Console.WriteLine("Max string: " + maxString);  //"banana"
    }
}

 上の例では、IComparable というインターフェースで制約をしました。ほかにも、ジェネリック型の制約には、次のようなものがあります。

制約 説明
where T : struct Tは値型
where T : class Tは参照型
where T : クラス名 Tは指定したクラスまたはその派生クラス
where T : インターフェイス Tは指定したインターフェースを継承するクラス

補足:CompareTo 関数

 CompareTo 関数は2つの値を比較する場合に用います。数値だけでなく、文字列や日付なども比較できます。

 戻り値は1, 0, -1 のいずれかになります。

a.CompareTo(b) 
//a>bの場合1
//a=bの場合0
//a<bの場合-1