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

4

5 回答 5

3

这至少是 TreeNode 类型。它可以是派生的,也可以完全是 TreeNode。SetParent 需要一个 T。但 T 可以是与 this 不同的类型。我们知道 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>

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

 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 的父级。可能是我不熟悉的一些深奥的算法,否则看起来不正确。

于 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电话抛出一个InvalidCastException说法Unable to cast object of type 'Bar' to type 'Foo'.

关于 CRTP 习惯用法 - 仅约定要求泛型类型与声明类型相同。该语言必须支持不遵循 CRTP 约定的其他情况。Eric Lippert 写了一篇关于这个主题的精彩博客文章,他通过 c# answer 从另一个 crtp链接。

综上所述,如果您将实现更改为此...

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类型TFoo类的情况下或TreeNode<T>Bar类情况下的子类Bar继承自TreeNode<Foo>-任何一种方式都允许我们省略强制转换SetParent并通过这种省略避免无效的强制转换异常,因为赋值在所有情况下都是合法的。这样做的代价是不再能够T像以前那样在所有地方自由使用,这牺牲了 CRTP 的大部分价值。

我的一位同事/朋友认为自己是语言/语言功能的新手,直到他可以诚实地说他“在愤怒中使用它”;也就是说,他对语言的了解足以让他感到沮丧,因为要么无法完成他需要的事情,要么这样做很痛苦。这很可能是其中一种情况,因为这里存在一些限制和差异,这与泛型不是模板这一事实相呼应。

于 2012-03-17T05:30:19.353 回答
0

当您使用引用类型时,并且您知道沿类型层次结构的强制转换将成功(这里没有自定义强制转换),那么实际上不需要强制转换任何东西。引用整数的值在强制转换之前和之后是相同的,那么为什么不跳过强制转换呢?

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

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

.NET 根本不会关心您没有转换值。Jitter 似乎只关心商店的大小是否一致,它们总是用于参考。

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

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

于 2015-10-29T16:59:36.590 回答