C# - 使用CRTP的侵入式树结构

时间:2012-02-20 22:53:08

标签: c# tree crtp

我目前正在研究一种在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#cast有一个成本,我不想在每个侵入式集合实现中传播这个演员......

5 个答案:

答案 0 :(得分: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'

答案 1 :(得分:0)

没有人保证Tthis的类型相同。它们甚至可以是TreeNode的无关子类。

您希望在奇怪的重复出现的模板模式中使用T,但通用约束无法表达这一点。

愚蠢的实现可以定义为StupidNode:TreeNode<OtherNode>

答案 2 :(得分:0)

问题在于这一行:

 TreeNode<T> where T : TreeNode<T>

作为TreeNode是递归定义,无法在预编译或甚至静态检查时确定。不要使用模板,或者你需要重构&amp;将节点与有效负载分开(即节点数据来自节点本身。)

 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)。似乎AddChild更恰当地命名为SetParent,因为您将此实例设置为a_node的父级。可能是我不熟悉的一些深奥的算法,否则它看起来不正确。

答案 3 :(得分: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来电引发InvalidCastException Unable to cast object of type 'Bar' to type 'Foo'.

关于CRTP习语 - 仅仅是约定,要求泛型类型与声明类型相同。该语言必须支持不遵循CRTP约定的其他情况。 Eric Lippert撰写了一篇关于这个主题的精彩博客文章,他与其他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类型T类型中的类型FooTreeNode<T>类案例中Bar的子类Bar继承TreeNode<Foo>来自SetParent - 无论哪种方式都允许我们省略T中的演员表,并且通过该省略避免了无效的演员表异常,因为在所有情况下作业都是合法的。执行此操作的成本不再能够在所有地方自由使用{{1}},因为之前使用过这会牺牲CRTP的大部分价值。

我的一位同事/朋友认为自己是语言/语言功能的新手,直到他能够诚实地说他“在愤怒中使用它”。也就是说,他非常了解这种语言,以至于无法完成他所需要的东西,或者这样做是痛苦的。这很可能是其中一种情况,因为这里存在限制和差异,这与generics are not templates的真相相呼应。

答案 4 :(得分:0)

当您使用引用类型时,您知道沿着类型层次结构的转换将成功(此处没有自定义转换),则无需实际转换任何内容。参与整数的值在演员之前和之后是相同的,那么为什么不跳过演员?

这意味着你可以在CIL / MSIL中编写这个被鄙视的AddChild方法。方法体操作码如下:

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

.NET根本不关心你没有投射价值。抖动似乎只关心商店的大小是否一致,它们总是作为参考。

加载Visual Studio的IL Support扩展(可能必须打开vsix文件并修改支持的版本),并使用MethodImpl.ForwardRef属性将C#方法声明为extern。然后只需在.il文件中重新声明该类,并添加您需要的一个方法实现,其主体在上面提供。

请注意,这也会手动将SetParent方法内联到AddChild。