2012-02-20 11 views
5

私は現在、C#で侵入型ツリー構造を実装する簡単な方法を開発中です。私は主にC++のプログラマーなので、すぐにCRTPを使いたいと思っていました。ここに私のコードは次のとおりです。C# - CRTPを使用した侵入型ツリー構造

public class TreeNode<T> where T : TreeNode<T> 
{ 
    public void AddChild(T a_node) 
    { 
     a_node.SetParent((T)this); // This is the part I hate 
    } 

    void SetParent(T a_parent) 
    { 
     m_parent = a_parent; 
    } 

    T m_parent; 
} 

これは動作しますが、私はジェネリック型の制約を使用していて、私は、((T)この)a_node.SetParentを呼び出すときにキャストする必要があり、なぜ...私が理解することはできません。.. 。 C#のキャストはコストを持っており、私は

+0

私はあなたの例を単純化することができました。同意しない場合は元に戻してください。 – usr

+1

正直言って、これは賢い頭のように見えます。代わりに、構図を使ってツリーを表現する伝統的な方法には何が問題なのですか?これは何をあなたに買うのですか?あまりにも敵対的ではないと思います。私はちょうど興味がある。 – spender

+0

@spender割り当ての数の半分になり、従う必要のある迂回の数が減ります。だから、高性能なコードでは、それは妥当なトレードオフかもしれません。小さな木の場合はおそらく悪い考えです。 – CodesInChaos

答えて

3

少なくともこれはTreeNodeタイプです。これは導出することも、正確にはTreeNodeすることもできます。 SetParentはTを想定していますが、Tはこれとは異なるタイプです。これとTはどちらもTreeNodeから派生していますが、それらは異なるタイプである可能性があります。

例:

class A : TreeNode<A> { } 
new TreeNode<A>() //'this' is of type 'TreeNode<A>' but required is type 'A' 
0

誰もTthisの種類が同じであることを保証しません...各侵入コレクションの実装では、このキャストを広めないようにしたいと思います。たとえ無関係なサブクラスであってもTreeNodeであってもかまいません。

奇妙に繰り返されるテンプレートパターンではTが使用されると予想されますが、一般的な制約では表現できません。

愚かな実装はStupidNode:TreeNode<OtherNode>と定義できます。

0

問題は、この行である:

TreeNode<T> where T : TreeNode<T> 

Tはツリーノードであることは、それがプレコンパイル決定あるいは静的にチェックすることができない再帰的な定義です。テンプレートを使用するか、またはあなたが行う場合は、&をリファクタリングする必要があるペイロードからノードを分離しないでください(ノード自体からすなわちノードデータを。)

public class TreeNode<TPayload> 
{ 
    TPayload NodeStateInfo{get;set;} 

    public void AddChild(TreeNode<TPayload> a_node) 
    { 
     a_node.SetParent(this); // This is the part I hate 
    } 

    void SetParent(TreeNode<TPayload> a_parent) 
    { 
    } 
} 

はまた、私はあなたがa_nodeを呼び出している理由はわからないんだけど.SetParent(this)。このインスタンスをa_nodeの親として設定しているため、AddChildはSetParentという名前になっているようです。それは私が慣れていないいくつかの秘密のアルゴリズムかもしれない、そうでなければ、それは正しく見えません。

+0

通常、コンポジションベースのコレクションが優れたアイデアですが、侵入型のコレクションが適しています。 'TreeNode ここで、T:TreeNode 'という制約がこの文脈で意味をなす。それは不思議な繰り返しのテンプレートパターンによって満足される。 'AddChild'の実装は私にとってもうまく見えます。ツリーでは、 'SetParent'または' AddChild'を明示的に実装し、もう一方は実装したものを呼び出すようにします。 – CodesInChaos

0

は、次のように上記のコードを呼び出して、我々は...

public class Foo : TreeNode<Foo> 
{ 
} 

public class Bar : TreeNode<Foo> // parting from convention 
{ 
} 

を書き込むことによって、CRTP規則から逸脱した場合に何が起こるか考えてみましょう...と:

var foo = new Foo(); 
var foobar = new Bar(); 
foobar.AddChild(foo); 

AddChild呼び出しがInvalidCastExceptionをスローしますUnable to cast object of type 'Bar' to type 'Foo'.

CRTPイディオムに関しては、ジェネリック型が宣言子と同じであることタイプです。この言語は、CRTP規約に従わない他のケースをサポートしなければならない。エリック・リッペルト氏は、このトピックに関する素晴らしいブログ記事を投稿しました。彼はこの他のブログからリンクしましたcrtp via c# answer

あなたはこれに実装を変更した場合、言ったことのすべて

...

public class TreeNode<T> where T : TreeNode<T> 
{ 
    public void AddChild(T a_node) 
    { 
     a_node.SetParent(this); 
    } 

    void SetParent(TreeNode<T> a_parent) 
    { 
     m_parent = a_parent; 
    } 

    TreeNode<T> m_parent; 
} 

を...以前InvalidCastExceptionを投げた上記のコードが動作するようになりました。この変更により、m_ParentのタイプはTreeNode<T>となります。 thisいずれかFooクラスの場合のようにタイプTまたはTreeNode<Foo>からBar継承しているのでBarクラスの場合TreeNode<T>のサブクラスを作る - のいずれかの方法は、私たちがSetParentでキャストを省略し、その不作為によって以来、無効なキャスト例外を回避することができます割り当てはすべてのケースで合法です。これを行うコストは、CRTPの価値の大部分を犠牲にして以前使用されていたように、もはや自由にTをすべての場所で自由に使用することができません。

私の同僚/友人は、彼が「怒りでそれを使用している」と正直に言うまで、自分自身を言語/言語機能の初心者とみなしています。つまり、彼は、彼が必要とするものを達成する方法がない、そうすることが苦痛であるという不満を感じるだけの言語を十分に知っています。これは非常によく、これらのケースの1つである可能性があります。ここには、generics are not templatesという真実をエコーする制限と相違があります。

0

参照型で作業しているときに、型階層に沿ったキャストが成功することがわかっている場合(ここではカスタムキャストなし)、実際にキャストする必要はありません。参照整数の値はキャストの前後で同じなので、なぜキャストをスキップしないのですか?

つまり、この軽蔑したAddChildメソッドをCIL/MSILに書くことができます。メソッド本体のオペコードは次のとおりです。

ldarg.1 
ldarg.0 
stfld TreeNode<class T>::m_parent 
ret 

.NETは値をキャストしないことをまったく気にしません。ジッターは、一貫性のある店舗のサイズのみを気にしているように見えます。

Visual Studio用のILサポート拡張機能をロードし(vsixファイルを開き、サポートされているバージョンを変更する必要があります)、MethodImpl.ForwardRef属性でC#メソッドをexternとして宣言します。次に、クラスを.ilファイルで再宣言し、必要なメソッド実装を1つ追加します。その本体は上記で提供されています。

これは、手動でSetParentメソッドをAddChildにインライン化することにも注意してください。

関連する問題