Как я могу ограничить дочерние узлы узлов в древовидной структуре

Я создаю древовидную структуру на основе класса 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
{

}

person Eric Anastas    schedule 04.11.2009    source источник


Ответы (4)


Может что-то вроде

public abstract class AbstractNode<T> //where T : AbstractNode
{

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

возможно работа? Просто не уверен в закомментированной части

EDIT: Это очень плохо внутри, но компилируется...

  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
  }
person mike    schedule 04.11.2009
comment
Да, это то, о чем я думал, за исключением того, что может быть эквивалент, где T часть в конкретном классе узла, который явно устанавливает T в какой-то другой конкретный тип узла. - person Eric Anastas; 04.11.2009
comment
Я согласен, что это очень плохо, но может ли кто-нибудь назвать более точную причину, почему я хотел бы или не хотел бы делать что-то подобное? - person Eric Anastas; 04.11.2009
comment
Подумайте также о составном шаблоне проектирования, это во многом то, что вы пытаетесь сделать (dofactory .com/patterns/patterncomposite.aspx#_self1) - person mike; 05.11.2009

К сожалению нет. Вы должны выбрать; вы хотите, чтобы свойство Children ConcreteNode было NodeCollection<AbstractNode> или NodeCollection<ConcreteNode>?

Проблема возникает, когда вы думаете о добавлении узла в свою коллекцию; что, если у вас есть ConcreteNodeA, который вы превратили в AbstractNode. Затем вы пытаетесь позвонить

concreteA_As_Abstract.Add(concreteB);

NodeCollection должен разрешать добавление; NodeCollection не будет. Таким образом, вы должны сделать выбор.

Новые функции ковариантности/контравариантности C#4 могут вам помочь (дополнительную информацию см. в блоге Эрика Липперта), но их не будет до VS2010.

person Steve Cooper    schedule 06.11.2009

Это напоминает мне кое-что, с чем я играл, и я добавлю сюда код, предлагающий вам принять его с «зерном соли»: я еще не полностью протестировал его, и для меня есть некоторые очень странные кое-что об этом коде (ищите комментарии "// странно :"). Это было сделано в бета-версии 2 VS Studio 2010, скомпилированной для 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]);

лучший, Билл

person BillW    schedule 05.11.2009

Это то, что вы хотите:

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>'
person emptyset    schedule 06.11.2009