C#のGeneric その2

前回Genericについてちょっと書いたり、最近Genericを使ったコードを書いたんで、
気づいたことをちょこちょこと書きます。


まず最初に、継承元もしくは実装するインタフェースがジェネリックの場合自分自身を型引数に渡すことができます。

interface I<T>
{
	void Test(T c);
}

class C : I<C>
{
	public void Test(C c) { }
}

これは割と当たり前。むしろできなかったらどうしようかと思った。
気をつけるのが、インタフェースに定義したメソッドTestを実装する場合、型Tの部分を型引数に渡した自分自身の型であるCに変えること。
まぁ当たり前の話です。


んで、こんな感じのことを利用して実際に使ったクラス設計を簡略化して書いてみます。

interface Interface<T>
{
	event Action<T> SomeEvent;
}

class ImplInterface : Interface<ImplInterface>
{
	public event Action<ImplInterface> SomeEvent;
	public String Text
	{
		get{regurn "ImplInterface!!";}
	}
}

abstract class AbstractClass<T> where T : Interface<T>, new()
{
	protected abstract void AbstMethod(T arg);
	
	private void SomeMethod()
	{
		T value = new T();
		value.SomeEvent += AbstMethod;
	}
}

class ImplClass : AbstractClass<ImplInterface>
{
	protected override void AbstMethod(ImplInterface arg)
	{
		MessageBox.Show(arg.Text);
	}
}

なんかこう、ジェネリックとインタフェースと抽象クラスが絡み合った感じです。


このコードの意図は共通部分は抽象クラスで行い特化した部分は実装クラスで行おうという、そのまんまなことです。
今回の場合共通部分は、決められたインタフェースを実装したクラスの生成と初期化です。
インタフェースに一つのイベントしかないのでイベントの追加しか行っていませんが、ほかのプロパティやメソッドを追加すれば管理や操作も行えます。
実際に使ったコードではそうしています。

abstract class AbstractClass<T> where T : Interface<T>, new()
{
	protected abstract void AbstMethod(T arg);
	
	private void SomeMethod()
	{
		T value = new T();
		value.SomeEvent += AbstMethod;
	}
}

まず抽象クラスをジェネリックにして、型引数に制約を付けます。
AbstractClassの型引数Tは、ジェネリックなインタフェースInterfaceに、受け取った型引数Tをそのまま渡したインタフェース制約を付けています。
ついでに型Tを生成するためにコンストラクタ制約も付けます。
そして引数にTをとる抽象メソッドAbstMethodを定義して、抽象でないSomeMethodでTを生成、初期化を行っています。
初期化の際、T型のSomeEventにアクセスして抽象メソッドをイベントとして登録します。


抽象クラスや抽象メソッドはあくまで抽象的なものなので、実装が必要です。
なのでImplClassの登場。

class ImplClass : AbstractClass<ImplInterface>
{
	protected override void AbstMethod(ImplInterface arg)
	{
		MessageBox.Show(arg.Text);
	}
}

ImplClassはAbstractClassを継承し、その際に型引数をInterfaceを実装したImplInterfaceを渡しています。
AbstractClassの型引数には制約がありますが、渡したクラスは全ての制約に当てはまるので問題はありません。


ImplClassでは抽象メソッドであるAbstMethodを実装しています。
このとき、抽象メソッドの段階では型がTだった部分がImplInterfaceに変わっています。
継承する段階でAbstractClassの型引数にImplInterfaceを渡しているためこれが可能になります。
そしてメソッドの実装ではInterfaceにはない、ImplInterface特有のTextプロパティを使っています。


要するに、


通化することが可能な部分をインタフェースにまとめそれだけを利用する抽象クラスを作り、
特化した部分はそれぞれの実装クラスに任せる。
そしてそれを柔軟に行うためにジェネリックを使う。


ということです。以上。


なにが『ということです』だよ。とか、
で何に使えるの?とか、
よくわかんない。とか、
そんな感じの文句というか当たり前な感想はナシの方向でお願いします。


僕の説明が下手だったり、実際に使えるのかどうかわかんなかったり、説明が中途半端なのはデフォルトです。
気にしない気にしない。


実際の話、この方法を今日思いついて、今日コーディングして、今日この記事を書いています。
なのでまだちゃんとした検証をしていないという、なんという投げっぱなし。
それでもまぁ、気にしない気にしない。