C# generics - why is an explicit cast needed when dealing with type parameters in this way?
me again. Sorry about this.
Following on from my previous question (I think I failed to adequately demonstrate the source of my confusion), here is the actual function I was writing:
/// <summary>
/// A b-tree node.
/// </summary>
public class BTreeNode
{
/// <summary>
/// Create a new b-tree node.
/// </summary>
public BTreeNode()
{
}
/// <summary>
/// The node name.
/// </summary>
public string Name
{
get;
set;
}
/// <summary>
/// The left-hand child node.
/// </summary>
public BTreeNode Left
{
get;
set;
}
/// <summary>
/// The right-hand child node.
/// </summary>
public BTreeNode Right
开发者_开发技巧 {
get;
set;
}
}
/// <summary>
/// Perform a breadth-first traversal of a b-tree.
/// </summary>
/// <param name="rootNode">
/// The b-tree's root node.
/// </param>
/// <param name="forEachNode">
/// An action delegate to be called once for each node as it is traversed.
/// Also includes the node depth (0-based).
/// </param>
public static void TraverseBreadthFirst<TNode>(this TNode rootNode, Action<TNode, int> forEachNode)
where TNode : BTreeNode
{
if (rootNode == null)
throw new ArgumentNullException("rootNode");
if (forEachNode == null)
throw new ArgumentNullException("forEachNode");
Queue<Tuple<TNode, int>> nodeQueue = new Queue<Tuple<TNode, int>>(3); // Pretty sure there are never more than 3 nodes in the queue.
nodeQueue.Enqueue(new Tuple<TNode, int>(rootNode, 0));
while (nodeQueue.Count > 0)
{
Tuple<TNode, int> parentNodeWithDepth = nodeQueue.Dequeue();
TNode parentNode = parentNodeWithDepth.Item1;
int nodeDepth = parentNodeWithDepth.Item2;
forEachNode(parentNode, nodeDepth);
nodeDepth++;
if (parentNode.Left != null)
nodeQueue.Enqueue(new Tuple<TNode, int>((TNode)parentNode.Left, nodeDepth));
if (parentNode.Right != null)
nodeQueue.Enqueue(new Tuple<TNode, int>((TNode)parentNode.Right, nodeDepth));
}
}
I am not sure I see why the explicit cast to TNode is needed here:
nodeQueue.Enqueue(new Tuple<TNode, int>((TNode)parentNode.Left, nodeDepth));
Under what circumstances could parentNode.Left be something that is not assignable to TNode (given TNode is constrained to be of type BTreeNode or derived).
To put the question another way, under what circumstances could this function result in an InvalidCastException? If there isn't one, then why does the compiler require an explicit cast?
Edit: I think I'm ok with changing the implementation to something like:
TNode leftNode = parentNode.Left as TNode;
Debug.Assert(leftNode != null || parentNode.Left == null, "Left child is more derived.");
if (leftNode != null)
nodeQueue.Enqueue(new Tuple<TNode, int>(leftNode, nodeDepth));
parentNode.Left
is only typed to be BTreeNode
. There's no guarantee that it's the same as TNode
. Imagine you had:
class SpecialBTreeNode : BTreeNode
class BoringBTreeNode : BTreeNode
Now consider TraverseBreadthFirst<SpecialBTreeNode>(rootNode, ...)
. What's to stop rootNode.Left
from returning a BoringBTreeNode
?
// This is entirely valid...
SpecialBTreeNode special = new SpecialBTreeNode();
special.Left = new BoringBTreeNode();
It sounds like you might want to make BTreeNode
itself generic:
public class BTreeNode<T> where T : BTreeNode<T>
{
public T Left { get; set; }
public T Right { get; set; }
}
Your parentNode.Left
is defined as BTreeNode
, not as TNode
. Even if you derive TNode:BTreeNode
, your .Left
is still a BTreeNode
reference, not a TNode
one. So you must cast. You need BTreeNode
class to be generic, as Jon Skeet pointed out.
精彩评论