5

私は現在、C# で侵入ツリー構造を実装する簡単な方法に取り組んでいます。私は主に 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;
}

これは機能しますが...ジェネリック型の制限を使用しているため、a_node.SetParent((T)this)を呼び出すときにキャストする必要がある理由がわかりません... C#キャストにはコストがかかります。各侵入コレクション実装でこのキャストを広めないでください...

4

5 に答える 5

3

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

例:

class A : TreeNode<A> { }
new TreeNode<A>() //'this' is of type 'TreeNode<A>' but required is type 'A'
于 2012-02-20T23:03:55.277 に答える
1

Tと の型thisが同じであることは誰も保証していません。の無関係なサブクラスになることさえありますTreeNode

不思議なことに繰り返されるテンプレート パターンで使用されることを期待Tしていますが、一般的な制約ではそれを表現できません。

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

于 2012-02-20T23:03:09.040 に答える
0

問題はこの行にあります:

 TreeNode<T> where T : TreeNode<T>

TreeNodeである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の方が適切なようです。それは私がよく知らないいくつかの難解なアルゴリズムかもしれません、さもなければそれは正しく見えません。

于 2012-02-20T23:24:07.043 に答える
0

書くことによってCRTPの慣習から逸脱するとどうなるか考えてみてください...

public class Foo : TreeNode<Foo>
{
}

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

...そして、上記のコードを次のように呼び出します。

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

コールは格言AddChildをスローしますInvalidCastExceptionUnable to cast object of type 'Bar' to type 'Foo'.

CRTP イディオムに関して - ジェネリック型が宣言型と同じであることを要求するのは、慣習だけです。言語は、CRTP 規則に従わない他のケースをサポートする必要があります。Eric Lippert は、このトピックに関するすばらしいブログ投稿を書きました。彼は、この他のcrtp から 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>ます。から継承するため、クラスの場合のようにthisTを作成するか、Fooクラスの場合のサブクラスを作成します-いずれの方法でも、キャストを省略でき、その省略により、割り当てがすべての場合に有効であるため、無効なキャスト例外を回避できます。これを行うコストは、CRTP の価値の多くを犠牲にする以前に使用されていたように、すべての場所で自由に使用できなくなったことです。TreeNode<T>BarBarTreeNode<Foo>SetParentT

私の同僚/友人は、「怒って使った」と正直に言うことができるまで、自分自身を言語/言語機能の初心者だと考えています。つまり、必要なことを達成する方法がないか、それを行うのが苦痛であることに不満を感じるほど十分に言語を知っています。ここには、ジェネリックはテンプレートではないという真実を反映した制限と違いがあるため、これはそのようなケースの 1 つになる可能性があります。

于 2012-03-17T05:30:19.353 に答える
0

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

つまり、この軽蔑された AddChild メソッドを CIL/MSIL で記述できるということです。メソッド本体のオペコードは次のとおりです。

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

.NET は、値をキャストしなかったことをまったく気にしません。ジッターはストアのサイズが一貫していることだけを気にしているようです。これは常に参照用です。

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

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

于 2015-10-29T16:59:36.590 に答える