【问题标题】:How can I restrict the children of nodes in a tree structure如何限制树结构中节点的子节点
【发布时间】:2010-12-12 21:22:10
【问题描述】:

我正在创建一个基于 AbstractNode 类的树结构。 AbstractNode 类具有包含其子节点的通用集合属性。请参阅下面的代码示例。

是否有某种方法(可能使用泛型)可以限制 AbstractNode 的具体版本只允许一种类型的子节点?请参阅下面的 ConcreteNodeA 代码,其中 ChildNodes 属性是 ConcreteNodeB 的集合,而不是 AbstractNode。这当然不能编译,但我想知道是否有其他方法可以达到相同的效果。

当然,一切都适用于 ChildNodes 集合属性始终是 AbstractNode 类型,但我正在尝试在我的类中嵌入一些关于什么的逻辑节点应该是其他节点的子节点。另外,在引用 ChildNodes 属性时,如果我不必将集合转换为我知道它应该是的类型的集合,那就太好了。

public abstract class AbstractNode
{

    public abstract NodeCollection<AbstractNode> ChildNodes
    {
        get;
        set;
    }
}

public class ConcreteNodeA : AbstractNode
{
    //THIS DOES NOT COMPLILE
    //Error 1   'ConcreteNodeA.ChildNodes': type must be 'NodeCollection<AbstractNode>' 
    //to match overridden member 'AbstractNode.ChildNodes'  
    public override NodeCollection<ConcreteNodeB> ChildNodes
    {
        get;
        set;
    }
}

public class ConcreteNodeB : AbstractNode
{
    public override NodeCollection<AbstractNode> ChildNodes
    {
        get;
        set;
    }
}

public class NodeCollection<T> : BindingList<T>
{ 
    //add extra events here that notify what nodes were added, removed, or changed
}

更新

好吧,我想我知道我想做什么,但我想知道是否有人认为这“感觉不好”或“闻起来很有趣”以及为什么。而不是我的节点具有 ChildNodes 集合属性,我正在考虑使每个节点成为实际的集合。所以我的树结构实际上只是一系列集合的集合。然后,我的抽象 Node 类将使用泛型上的各种约束来控制它可以拥有的子节点的种类。

这有意义吗?我有理由不想这样做吗?我以前从未在自己的一个类中使用过泛型,所以我不确定我是否忽略了某些东西。

public interface INode
{

}

public abstract class AbsNode<T> : BindingList<T>, INode where T : INode
{

}

public abstract class AbsNodeA<T> : AbsNode<T> where T : AbsSubNodeA
{

}

public abstract class ConcreteNodeA : AbsNodeA<AbsSubNodeA>
{

}

public abstract class AbsSubNodeA : INode
{

}

public class ConcreteSubNodeA :AbsSubNodeA
{

}

public class ConcreteSubNodeB :AbsSubNodeA
{

}

【问题讨论】:

    标签: c# generics data-structures tree


    【解决方案1】:

    很遗憾没有。你必须选择;您希望 ConcreteNode 的 Children 属性为 NodeCollection&lt;AbstractNode&gt; 还是 NodeCollection&lt;ConcreteNode&gt;?

    当您考虑将节点添加到您的集合时,问题就出现了;如果你有一个ConcreteNodeA,你已经将它转换为AbstractNode。然后,您尝试调用

    concreteA_As_Abstract.Add(concreteB);
    

    NodeCollection 应该允许添加; NodeCollection 不会。所以你必须做出选择。

    新的 C#4 协变/逆变功能可能会对您有所帮助(请参阅 Eric Lippert 的博客了解更多信息),但它们直到 VS2010 才出现。

    【讨论】:

      【解决方案2】:

      可以类似

      public abstract class AbstractNode<T> //where T : AbstractNode
      {
      
        public abstract NodeCollection<T> ChildNodes
        {
          get;
          set;
        }
      }
      

      可能工作?只是不确定注释掉的部分

      编辑:这感觉真的很糟糕,但它编译...

        public abstract class BaseNode
        {
        }
      
        public abstract class AbstractNode<T> : BaseNode where T : BaseNode
        {
          public abstract NodeCollection<T> ChildNodes
          {
            get;
            set;
          }
        }
      
        public class ConcreteNodeA : AbstractNode<ConcreteNodeA>
        {
          public void Special() { }
      
          public override NodeCollection<ConcreteNodeA> ChildNodes
          {
            get;
            set;
          }
        }
      
        public class ConcreteNodeB : AbstractNode<ConcreteNodeA>
        {
          public void DoSomething()
          {
            ChildNodes[0].ChildNodes[0].ChildNodes[0].Special();
          }
      
          public override NodeCollection<ConcreteNodeA> ChildNodes
          {
            get;
            set;
          }
        }
      
        public class NodeCollection<T> : BindingList<T>
        {
          //add extra events here that notify what nodes were added, removed, or changed
        }
      

      【讨论】:

      • 是的,我就是这么想的,只是具体节点类中可能有一个等效的“where T”部分,它将 T 显式设置为其他一些具体节点类型
      • 我同意“感觉真的很糟糕”,但有人能给出更确切的理由,为什么我会或不想做这样的事情吗?
      • 想想复合设计模式,这正是你想要做的事情 (dofactory.com/patterns/patterncomposite.aspx#_self1)
      【解决方案3】:

      这让我想起了我一直在玩的东西,我将在这里输入代码,建议你用“一粒盐”来对待它:我还没有完全测试它,对我来说,关于这段代码的一些非常奇怪的事情(寻找“//奇怪的:”cmets)。这是在 VS Studio 2010 beta 2 中完成的,针对 FrameWork 4.0 编译。

      using System;
      using System.Collections.Generic;
      using System.Linq;
      
      // WARNING : EXPERIMENTAL CODE : DO NOT USE FOR ANYTHING BUT EDUCATIONAL PURPOSES
      
      // comments about how crazy the code is : are welcome :)
      
      namespace stronglyTypedTree
      {
          // TreeNodes is a strongly typed List of strongly typed Nodes
          public class TreeNodes<T> : List<Node<T>>
          {
              // weird : sometimes the compiler informs me that new is
              // required, if i have not used new, and sometimes it informs
              // me, when I have added new, that new is not required
              public new void Add(Node<T> newNode)
              {
                  Console.WriteLine("Add called in TreeNodes class : Type = " + typeof(T).ToString() + " : Node Key = " + newNode.Key.ToString());
                  newNode.Parent = this;
                  base.Add(newNode);
              }
          }
      
          // strongly typed Node
          public class Node<T>
          {
              // note : implement a key/value pair
              // instead of this ?
              internal T _key;
      
              // experimental : have not fully considered
              // the case of root nodes
      
              // better to make this a property ?
              public TreeNodes<T> Parent;
      
              // better to make this a property ?
              public TreeNodes<T> Nodes;
      
              public Node()
              {
                  Nodes = new TreeNodes<T>();
              }
      
              // weird : calling base() here does NOT seem to call the
              // parameterless ctor above : the Nodes collection is, thus, 
              // not instantiated : will cause errors at run-time !
              public Node(T keyValue) : base()
              {
                  _key = keyValue;
                  // had to insert this : see note above
                  Nodes = new TreeNodes<T>();
              }
      
              public T Key
              {
                  get { return _key; }
                  set { _key = value; }
              }
          }
      
          public class Tree<T>
          {
              public TreeNodes<T> Nodes;
      
              public string Name;
      
              public Tree()
              {
                  Nodes = new TreeNodes<T>();
              }
      
              // weird : see note on ctor with one parameter
              // in the Node class above
              public Tree(string treeName) : base()
              {
                  Name = treeName;
                  // had to insert this : see note above
                  Nodes = new TreeNodes<T>();
              }
          }
      
          // define some strongly typed Node classes
      
          // weird : i thought i could get away with not defining explicit ctors :
          // that ctor's of the Node class would be automatically invoked
          public class intNode : Node<int>
          {
              public intNode() : base() { }
      
              public intNode(int keyValue) : base(keyValue) { }
          }
      
          public class strNode : Node<string>
          {
              public strNode() : base() { }
      
              public strNode(string keyValue) : base(keyValue) { }
          }
      }
      

      一些示例测试调用:

          intNode myIntNode1 = new intNode();
          myIntNode1.Key = 100;
          intNode myIntNode2 = new intNode(777);
      
          strNode myStrNode1 = new strNode();
          myStrNode1.Key = "hello";
          strNode myStrNode2 = new strNode("string node 2");
      
          Tree<int> intTree = new Tree<int>();
          intTree.Name = "Tree of Integer";
      
          Tree<string> strTree = new Tree<string>("Tree of String");
      
          intTree.Nodes.Add(myIntNode1);
          intTree.Nodes.Add(myIntNode2);
      
          strTree.Nodes.Add(myStrNode1);
          strTree.Nodes.Add(myStrNode2);
      
          myIntNode1.Nodes.Add(new intNode(999));
          myStrNode2.Nodes.Add(new strNode("subNode of strNode2"));
      
          Console.WriteLine(intTree.Nodes.Count);
      
          Console.WriteLine(intTree.Nodes[0]);
      
          Console.WriteLine(strTree.Nodes.Count);
      
          Console.WriteLine(strTree.Nodes[1]);
      

      最好的,比尔

      【讨论】:

        【解决方案4】:

        这是你想要的吗:

        namespace Foo 
        {   
            public interface INode
            {
                string Speak();
            }
        
            public abstract class AbstractRoot<T> where T : INode
            {
                public abstract IList<T> Children { get; set; }
            }
        
            public class GammaChild : INode
            {
                public string Speak() { return "I am GammaNode."; }
            }
        
            public class BetaChild : AbstractRoot<BetaChild>, INode
            {
                public string Speak() { return "I am BetaNode."; }
                public string BetaSpeak() { return "I am talking Beta-specific things."; }
        
                private IList<BetaChild> children;
                public override IList<BetaChild> Children { get { return children; } set { children = value; } }
            }
        
            public class AlphaRoot<T> : AbstractRoot<T>, INode where T : BetaChild
            {
                public string Speak() { return "I am AlphaRoot."; }
        
                private IList<T> children;
                public override IList<T> Children { get { return children; } set { children = value; } }
            }
        
            public class Test
            {
                public void Run()
                {
                    AlphaRoot<BetaChild> alphaBetaTree = new AlphaRoot<BetaChild>();
                    alphaBetaTree.Children.Add(new BetaChild());
        
                    alphaBetaTree.Children[0].BetaSpeak();
        
                    AlphaRoot<GammaChild> alphaGammaTree = new AlphaRoot<GammaChild>();
                    alphaGammaTree.Children.Add(new GammaChild());
                }
            }
        }
        

        正如预期的那样,尝试将树用于 GammaChild 时的编译错误是:

        The type 'Foo.GammaChild' must be convertible to 'Foo.BetaChild' in order to use it as parameter 'T' in the generic type or method 'Foo.AlphaRoot<T>'
        

        【讨论】:

          猜你喜欢
          • 2015-05-15
          • 2011-09-10
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多