我目前正在研究一种在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有一个成本,我不想在每个侵入式集合实现中传播这个演员......
答案 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)
没有人保证T
和this
的类型相同。它们甚至可以是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
类型中的类型Foo
或TreeNode<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。