简单的泛型树






4.25/5 (15投票s)
C# 中的简单泛型树
public abstract class TreeNodeBase<T> : ITreeNode<T> where T : class, ITreeNode<T>
引言
我想为现有的树类创建一个抽象基类。这看起来像一个快速的复制粘贴工作,但结果并没有我想象的那么简单。
基类必须能够完成所有基本的树处理,例如添加子节点、获取树的根节点、获取当前节点的父节点等。具体类必须能够扩展和使用基类的树功能,而且我不想进行任何类型转换。我想要一个实现了所有树的标准功能的基类,而不是一组接口。
背景
在复制现有类以形成抽象基类的基础后,我很快删除了特定于具体类的代码,从而保留了基本的树代码。我很快将其转换为泛型类T
,然后乐趣就开始了。在这个阶段,我开始遇到一些问题,所以我想看看 CodeProject。我很快找到了一些其他的文章,但它们没有满足我所有的标准。它们通常是节点的包装类,因此具体类不知道树。
入门
经过几次尝试失败后,我想尝试一些我不确定使用泛型是否可能的事情。我决定让具体类继承自它自己的泛型类。它看起来编译通过了,所以我想我将继续沿着这条路走下去,看看它会带我走向何方。
所以我的泛型树的开始是这样的
//------------------------------------------------------------------------------
/// <summary>
/// Generic Tree Node base class
/// </summary>
/// <typeparam name="T"></typeparam>
//------------------------------------------------------------------------------
public abstract class TreeNodeBase<T>
//------------------------------------------------------------------------------
/// <summary>
/// Concrete class that inherits from the TreeNode Base
/// </summary>
//------------------------------------------------------------------------------
public class SpecialTreeNode : TreeNodeBase<SpecialTreeNode>
好的,是时候在骨头上加点肉了。我添加了子节点列表和父节点的属性以及当前节点的名称。一切看起来都很好。我继续,并实现了IsLeaf
和IsRoot
的属性,而且看起来还不错。
所以我的基类现在看起来像这样
//------------------------------------------------------------------------------
/// <summary>
/// Generic Tree Node base class
/// </summary>
/// <typeparam name="T"></typeparam>
//------------------------------------------------------------------------------
public abstract class TreeNodeBase<T>
{
//------------------------------------------------------------------------------
/// <summary>
///
/// </summary>
/// <param name="name"></param>
//------------------------------------------------------------------------------
protected TreeNodeBase(string name)
{
Name = name;
ChildNodes = new List<T>();
}
//------------------------------------------------------------------------------
/// <summary>
/// Name
/// </summary>
//------------------------------------------------------------------------------
public string Name
{
get;
private set;
}
//------------------------------------------------------------------------------
/// <summary>
/// Parent
/// </summary>
//------------------------------------------------------------------------------
public T Parent
{
get;
set;
}
//------------------------------------------------------------------------------
/// <summary>
/// Children
/// </summary>
//------------------------------------------------------------------------------
public List<T> ChildNodes
{
get;
private set;
}
//------------------------------------------------------------------------------
/// <summary>
/// True if a Leaf Node
/// </summary>
//------------------------------------------------------------------------------
public bool IsLeaf
{
get { return ChildNodes.Count == 0; }
}
//------------------------------------------------------------------------------
/// <summary>
/// True if the Root of the Tree
/// </summary>
//------------------------------------------------------------------------------
public bool IsRoot
{
get { return Parent == null; }
}
}
问题 1:this
我准备添加一个方法来将子节点添加到当前节点,但很快发现了我的第一个问题。我无法将新子节点的父节点设置为this
,因为它无法将this
转换为类型T
。
//------------------------------------------------------------------------------
/// <summary>
/// Add a Child Tree Node
/// </summary>
/// <param name="child"></param>
//------------------------------------------------------------------------------
public void AddChild(T child)
{
child.Parent = this; // Cant do this as cannot cast this to Type T
ChildNodes.Add(child);
}
//------------------------------------------------------------------------------
/// <summary>
/// Add a collection of child Tree Nodes
/// </summary>
/// <param name="children"></param>
//------------------------------------------------------------------------------
public void AddChildren(IEnumerable<T> children)
{
foreach (T child in children)
AddChild(child);
}
这通过从具体类中实现的属性获取this
来解决。
在基类中
//------------------------------------------------------------------------------
/// <summary>
/// this
/// </summary>
//------------------------------------------------------------------------------
protected abstract T MySelf
{
get;
}
在具体类中的实现
protected override SpecialTreeNode MySelf
{
get { return this; }
}
因此,现在无论基类需要this
,我都可以使用MySelf
。
public void AddChild(T child)
{
child.Parent = MySelf;
ChildNodes.Add(child);
}
问题 2:什么是 T
我准备实现下一个方法,该方法是从当前节点获取根节点,但Parent
节点的类型是T
,而T
在基类中具有未知的实现。
//------------------------------------------------------------------------------
/// <summary>
/// Get the Root Node of the Tree
/// </summary>
//------------------------------------------------------------------------------
public T GetRootNode()
{
if (Parent == null)
return MySelf;
return Parent.GetRootNode(); // T does not contain a definition for GetRootNode
}
解决这个问题基本上就是解决这个难题。我为基类需要知道的属性和方法实现了一个泛型接口。好的一点是,基类实现了接口,这允许基类了解自己(如果你知道我的意思)。
界面
//------------------------------------------------------------------------------
/// <summary>
///
/// </summary>
/// <typeparam name="T"></typeparam>
//------------------------------------------------------------------------------
public interface ITreeNode<T>
{
//------------------------------------------------------------------------------
/// <summary>
///
/// </summary>
//------------------------------------------------------------------------------
T Parent { get; set; }
//------------------------------------------------------------------------------
/// <summary>
///
/// </summary>
//------------------------------------------------------------------------------
bool IsLeaf { get; }
//------------------------------------------------------------------------------
/// <summary>
///
/// </summary>
//------------------------------------------------------------------------------
bool IsRoot { get; }
//------------------------------------------------------------------------------
/// <summary>
///
/// </summary>
//------------------------------------------------------------------------------
T GetRootNode();
//------------------------------------------------------------------------------
/// <summary>
///
/// </summary>
//------------------------------------------------------------------------------
string GetFullyQualifiedName();
}
基类声明现在看起来像这样
public abstract class TreeNodeBase<T> : ITreeNode<T> where T : class, ITreeNode<T>
这允许我完成基类方法。它现在看起来像这样
//------------------------------------------------------------------------------
/// <summary>
/// Generic Tree Node base class
/// </summary>
/// <typeparam name="T"></typeparam>
//------------------------------------------------------------------------------
public abstract class TreeNodeBase<T> : ITreeNode<T> where T : class, ITreeNode<T>
{
//------------------------------------------------------------------------------
/// <summary>
///
/// </summary>
/// <param name="name"></param>
//------------------------------------------------------------------------------
protected TreeNodeBase(string name)
{
Name = name;
ChildNodes = new List<T>();
}
//------------------------------------------------------------------------------
/// <summary>
/// Name
/// </summary>
//------------------------------------------------------------------------------
public string Name
{
get;
private set;
}
//------------------------------------------------------------------------------
/// <summary>
/// Parent
/// </summary>
//------------------------------------------------------------------------------
public T Parent
{
get;
set;
}
//------------------------------------------------------------------------------
/// <summary>
/// Children
/// </summary>
//------------------------------------------------------------------------------
public List<T> ChildNodes
{
get;
private set;
}
//------------------------------------------------------------------------------
/// <summary>
/// this
/// </summary>
//------------------------------------------------------------------------------
protected abstract T MySelf
{
get;
}
//------------------------------------------------------------------------------
/// <summary>
/// True if a Leaf Node
/// </summary>
//------------------------------------------------------------------------------
public bool IsLeaf
{
get { return ChildNodes.Count == 0; }
}
//------------------------------------------------------------------------------
/// <summary>
/// True if the Root of the Tree
/// </summary>
//------------------------------------------------------------------------------
public bool IsRoot
{
get { return Parent == null; }
}
//------------------------------------------------------------------------------
/// <summary>
/// List of Leaf Nodes
/// </summary>
//------------------------------------------------------------------------------
public List<T> GetLeafNodes()
{
return ChildNodes.Where(x => x.IsLeaf).ToList();
}
//------------------------------------------------------------------------------
/// <summary>
/// List of Non Leaf Nodes
/// </summary>
//------------------------------------------------------------------------------
public List<T> GetNonLeafNodes()
{
return ChildNodes.Where(x => !x.IsLeaf).ToList();
}
//------------------------------------------------------------------------------
/// <summary>
/// Get the Root Node of the Tree
/// </summary>
//------------------------------------------------------------------------------
public T GetRootNode()
{
if (Parent == null)
return MySelf;
return Parent.GetRootNode();
}
//------------------------------------------------------------------------------
/// <summary>
/// Dot separated name from the Root to this Tree Node
/// </summary>
//------------------------------------------------------------------------------
public string GetFullyQualifiedName()
{
if (Parent == null)
return Name;
return string.Format("{0}.{1}", Parent.GetFullyQualifiedName(), Name);
}
//------------------------------------------------------------------------------
/// <summary>
/// Add a Child Tree Node
/// </summary>
/// <param name="child"></param>
//------------------------------------------------------------------------------
public void AddChild(T child)
{
child.Parent = MySelf;
ChildNodes.Add(child);
}
//------------------------------------------------------------------------------
/// <summary>
/// Add a collection of child Tree Nodes
/// </summary>
/// <param name="children"></param>
//------------------------------------------------------------------------------
public void AddChildren(IEnumerable<T> children)
{
foreach (T child in children)
AddChild(child);
}
}
唯一让我不太满意的是,由于接口,父节点有一个公共的 setter。
具体类的特化
为了证明它满足我所有最初的标准,我必须向我的具体类添加一些特化,并看看我是否真的可以从中利用基类。所以我添加了一个名为IsSpecial
的属性和几个方法来获取特殊节点(即,其中IsSpecial
为 true 的子节点),以及另一个方法来获取特殊的叶节点。这将证明它知道树,并且可以使用基类属性。
//------------------------------------------------------------------------------
/// <summary>
///
/// </summary>
//------------------------------------------------------------------------------
public class SpecialTreeNode : TreeNodeBase<SpecialTreeNode>
{
//------------------------------------------------------------------------------
/// <summary>
///
/// </summary>
/// <param name="name"></param>
//------------------------------------------------------------------------------
public SpecialTreeNode(string name)
: base(name)
{
}
//------------------------------------------------------------------------------
/// <summary>
///
/// </summary>
//------------------------------------------------------------------------------
protected override SpecialTreeNode MySelf
{
get { return this; }
}
//------------------------------------------------------------------------------
/// <summary>
///
/// </summary>
//------------------------------------------------------------------------------
public bool IsSpecial
{
get;
set;
}
//------------------------------------------------------------------------------
/// <summary>
///
/// </summary>
/// <returns></returns>
//------------------------------------------------------------------------------
public List<SpecialTreeNode> GetSpecialNodes()
{
return ChildNodes.Where(x => x.IsSpecial).ToList();
}
//------------------------------------------------------------------------------
/// <summary>
///
/// </summary>
/// <returns></returns>
//------------------------------------------------------------------------------
public List<SpecialTreeNode> GetSpecialLeafNodes()
{
return ChildNodes.Where(x => x.IsSpecial && x.IsLeaf).ToList();
}
//------------------------------------------------------------------------------
/// <summary>
///
/// </summary>
/// <returns></returns>
//------------------------------------------------------------------------------
public List<SpecialTreeNode> GetSpecialNonLeafNodes()
{
return ChildNodes.Where(x => x.IsSpecial && !x.IsLeaf).ToList();
}
}
使用代码
要使用基类,只需创建一个具体类并从TreeNoderBase
类继承,其中泛型类型是你的具体类。
public class SpecialTreeNode : TreeNodeBase<SpecialTreeNode>
你现在应该有一个功能齐全的树,你可以开始特化。(从上面)我实现了SpecialTreeNode
类,并执行了一些测试以查看是否一切都已检查完毕。
你可以在源代码中看到测试,但这是结果
关注点
我喜欢该类继承自它自己的泛型类。我也喜欢基类依赖于泛型接口来了解自己,以便它可以实现该接口。这一切都非常循环,但最终非常漂亮。
历史
- v1.0 2012年3月12日:初始发布。