65.9K
CodeProject 正在变化。 阅读更多。
Home

简单的泛型树

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.25/5 (15投票s)

2012年3月12日

CPOL

4分钟阅读

viewsIcon

45042

downloadIcon

966

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>

好的,是时候在骨头上加点肉了。我添加了子节点列表和父节点的属性以及当前节点的名称。一切看起来都很好。我继续,并实现了IsLeafIsRoot的属性,而且看起来还不错。

所以我的基类现在看起来像这样

//------------------------------------------------------------------------------
/// <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日:初始发布。
© . All rights reserved.