trsing’s diary

勉強、読んだ本、仕事で調べたこととかのメモ。

EFFECTIVE C# 6.0/7.0 読書メモ 項目23

項目23 型パラメータにおけるメソッドの制約をデリゲートとして定義する

たいていの場合、制約としてクラス制約やインターフェイス制約を指定する方法が適している。.NET BCLでもIComparable<T>などが実装されていることを期待する機能が多数ある。

しかし、独自の制約を作成する場合は、インターフェイスとして定義するより、デリゲート型を定義するほうが手間を省くことができる。

インターフェイスで制限する場合

ジェネリッククラスの作成者はインターフェイスを用意、実装する。ジェネリッククラスの使用者はインターフェイスを実装するクラスを作成する。

//Addメソッドを持つインターフェイス
public interface IAdd<T>
{
    T Add(T obj);
}
//型TにAdd()メソッドを必要とするジェネリッククラス
public static class Example<T> where T : IAdd<T>
{
    public static T Add(T left, T right) => left.Add(right);
}
//IAddを実装しているクラス
public class SumAdd : IAdd<SumAdd>
{
    public int Value { get; set; }
    public SumAdd(int value) => this.Value = value;
    public SumAdd Add(SumAdd obj) => new SumAdd(obj.Value + this.Value);
}
var a = new SumAdd(6);
var b = new SumAdd(7);
var sum = Example<SumAdd>.Add(a, b);

デリゲートを定義する場合

ジェネリッククラスの作成者は ジェネリッククラスで呼び出したいメソッドに一致するシグネチャを持ったデリゲートを定義する。ジェネリッククラスの使用者は使用時にメソッドを定義する。

//必要なシグネチャを持ったデリゲートを定義
public static class Example
{
    public static T Add<T>(T left, T right, Func<T, T, T> AddFunc) => AddFunc(left, right);
}
int a = 6;
int b = 7;
//使用時にメソッドを定義
int sum = Example.Add(a, b, (x, y) => x + y);

デリゲートとして登録されたメソッドを複数個所で呼び出すような場合

クラスのインスタンスを生成する際に、クラスのメンバをジェネリック型のデリゲートとして登録する。

public class InputCollection<T>
{
    //略
    privaete readonly CreateFromStream<T> readFunc;
    public InputCollection(Create FromStream<T> readFunc) => this.readFunc = readFunc;
    public void ReadFromStream(TextReader reader) => thingsRead.Add(readFunc(reader));
    //略
}