C# 複数の型パラメータを持つジェネリクス

ジェネリクスでは複数の型パラメータを持つクラスやメソッドを定義できます。Dictionary<TKey, TValue> はその代表例で、キーと値それぞれに異なる型を指定できます。

複数の型パラメータを持つクラス

型パラメータはカンマで区切って複数指定します。

public class KeyValuePair&lt;TKey, TValue&gt;
{
    public TKey Key { get; }
    public TValue Value { get; }

    public KeyValuePair(TKey key, TValue value)
    {
        Key = key;
        Value = value;
    }
}
var pair1 = new KeyValuePair&lt;string, int&gt;("Age", 25);
var pair2 = new KeyValuePair&lt;int, bool&gt;(1, true);

Console.WriteLine($"{pair1.Key}: {pair1.Value}"); // Age: 25

実用的な例:Result 型

成功/失敗を表現する Result 型は、複数の型パラメータの良い例です。

public class Result&lt;TSuccess, TError&gt;
{
    public TSuccess? SuccessValue { get; }
    public TError? ErrorValue { get; }
    public bool IsSuccess { get; }

    private Result(TSuccess? success, TError? error, bool isSuccess)
    {
        SuccessValue = success;
        ErrorValue = error;
        IsSuccess = isSuccess;
    }

    public static Result&lt;TSuccess, TError&gt; Success(TSuccess value)
        =&gt; new(value, default, true);

    public static Result&lt;TSuccess, TError&gt; Failure(TError error)
        =&gt; new(default, error, false);
}
Result&lt;int, string&gt; Divide(int a, int b)
{
    if (b == 0)
        return Result&lt;int, string&gt;.Failure("ゼロで除算できません");
    return Result&lt;int, string&gt;.Success(a / b);
}

var result = Divide(10, 2);
if (result.IsSuccess)
    Console.WriteLine($"結果: {result.SuccessValue}");
else
    Console.WriteLine($"エラー: {result.ErrorValue}");

型パラメータ間の関係を制約で表現

型パラメータ同士に関係を持たせることもできます。

public class Converter&lt;TInput, TOutput&gt;
    where TOutput : new()
{
    private readonly Func&lt;TInput, TOutput&gt; _convertFunc;

    public Converter(Func&lt;TInput, TOutput&gt; convertFunc)
    {
        _convertFunc = convertFunc;
    }

    public TOutput Convert(TInput input) =&gt; _convertFunc(input);
}

3つ以上の型パラメータ

型パラメータは3つ以上でも問題ありません。ただし、多くなりすぎると可読性が下がります。

public class Triple&lt;T1, T2, T3&gt;
{
    public T1 First { get; set; }
    public T2 Second { get; set; }
    public T3 Third { get; set; }

    public Triple(T1 first, T2 second, T3 third)
    {
        First = first;
        Second = second;
        Third = third;
    }
}

var triple = new Triple&lt;string, int, bool&gt;("Test", 42, true);

型パラメータの命名

複数の型パラメータを持つ場合は、役割がわかる名前を付けると良いでしょう。

汎用的な命名

T1, T2, T3 のような連番。短いが意味が伝わりにくい。

説明的な命名

TKey, TValue, TInput, TOutput など。役割が明確になる。

// 良い例:役割がわかる名前
public class Transformer&lt;TSource, TDestination&gt; { }
public class Cache&lt;TKey, TValue&gt; { }

// 避けたい例:意味がわかりにくい
public class Processor&lt;T1, T2, T3&gt; { }

複数の型パラメータを使いこなすことで、より汎用的で再利用性の高いコンポーネントを設計できます。