2009年2月26日木曜日

継承される事が前提のクラスに、自分が何者か教える方法

スキップリストのライブラリを作る時、ノードクラスには、ユーザがカスタムして使う事を前提にバーチャルメソッドをいくつか用意した。この時、スキップリストのコンテナクラスは、アイテムを引数として受け取り、ノードを生成してリストに追加するメソッドを持つ。問題は、この、ノードの生成という処理はノードクラスの静的メソッドであるべきだという事だ。すると、ノードクラスの基底クラスに、型パラメタとして、``自分が何者であるかを教えてやる''必要が生じる。少し変態的なコードになるが、面白いテクニックなのでまとめておく。

まず、スキップリストのコンテナクラスはこんな感じだ。

public class SkipList<TItem, TNode>
where TItem:class
where TNode:SkipListNode<TItem, TNode>, new()
{
TNode header, footer;
public SkipList()
{
header=SkipListNode<TItem, TNode>.CreateHeader();
footer=SkipListNode<TItem, TNode>.CreateFooter();
...
}
public void Insert(TItem item){
...
TNode newNode=SkipListNode<TItem, TNode>.CreateNode(item);
}
}

んで、ノードクラスはこんな感じだ。

public class SkipListNode<TItem, TNode>
where TItem:class
where TNode:SkipListNode<TItem, TNode>, new()
{
private TNode thisNode;
private TItem item;
private bool isHeader, isFooter;
public static TNode CreateNode(TItem item)
{
TNode newNode=new TNode();
newNode.thisNode=newnode;
newNode.item=Item;
newNode.isHeader=false;
newNode.isFooter=false;
return newNode;
}
public static TNode CreateHeader()
{
TNode newNode=new TNode();
newNode.thisNode=newnode;
newNode.item=null;
newNode.isHeader=true;
newNode.isFooter=false;
return newNode;
}
public static TNode CreateFooter()
{
TNode newNode=new TNode();
newNode.thisNode=newnode;
newNode.item=null;
newNode.isHeader=false;
newNode.isFooter=true;
return newNode;
}
...
他のメソッド(TNode型での自身への参照が欲しければthisでなくthisNodeフィールドを使う)
...
バーチャルなメソッドとかバーチャルなプロパティとか
}

使い方としては、

MyNode:SkipListNode<MyItem, MyNode>{
オーバーライドしたりプロパティを追加したり
}
...
クラスの定義の中で
SkipList<MyItem, MyNode> myList=new SkipList<MyItem, MyNode>();
...

などと使う。

特に、SkipListNode<TItem, TNode>の定義中でthisと書くとその型はSkipListNode<TItem, TNode>型になり、TNode型にならないので、TNode型のthisNodeフィールドを用意する事で、実行時の余計なキャストが必要なくなる。さりげないがここ超重要。

0 件のコメント: