2010-10-02 5 views
4

В C# У меня есть навязчивым древовидную структуру, которая выглядит следующим образом:Как сделать интрузивный древовидный класс в C# использовать дженерики?

public abstract class Node 
{ 
    Container parent; 
    Node nextNode; 
    Node previousNode; 

    public abstract class Container : Node 
    { 
     Node firstChild; 
     Node lastChild; 
    } 
} 

Различные объекты, которые могут быть добавлены к дереву наследоваться от обоих Node или Container в зависимости от того, могут ли они иметь детей или нет.

Посредством создания класса Container внутренний класс означает, что он может получить доступ к частным членам в Node для управления списком детей в контейнере.

Это все хорошо и хорошо. Но теперь я хочу сделать его общим, чтобы я мог повторно использовать его при сохранении безопасности типов - в основном, перемещая всю функциональность дерева в общий класс выше узла, а другой между узлами и контейнером. Вот грубый дизайн того, что я пытаюсь сделать:

public abstract class GenericNode<Node, Container> 
    where Node : GenericNode<Node, Container> 
    where Container : GenericNode<Node, Container>.GenericContainer 
{ 
    Container parent; 
    Node nextNode; 
    Node previousNode; 

    public abstract class GenericContainer : Node 
    { 
     Node firstChild; 
     Node lastChild; 
    } 
} 

Что, конечно, не работает, потому что вы не можете сделать GenericContainer наследовать от Node (ошибки компилятора CS0689). Даже если я откажусь от внутреннего требования к классу (скажем, используя internal и просто будучи осторожным в своей собственной библиотеке), я все еще не могу понять дизайн, который не сталкивается с той же проблемой (и ошибкой).

(Я не думал, что мне нужно, но просто для того, чтобы его прописать: Я не пытаюсь «исправить» ошибку компиляции, и я не ищу простой реализации дерева. дизайн вопрос.)

Итак, теперь я немного в тупике. Есть ли у кого-нибудь лучшие идеи о том, как это сделать?

Редактировать: Не забудьте взглянуть на this answer, что является еще одним ударом по дизайну, который пытается использовать методы расширения, чтобы избежать проблемы «инъекции» класса в иерархию наследования (но, к сожалению, не полностью Работа).

+0

Да, ошибка компиляции из-за вашего наименования. Просто назовите параметры типа TNode и TContainer, и он просто компилирует ... –

+0

@Claus: Нет, это не так, и нет, это не так (причина, по которой ваш ответ компилируется, заключается в том, что вы изменили то, что наследует GenericContainer). –

+0

Да, конечно. Но я все же хотел указать, почему у вас возникла ошибка CS0689, потому что у вас были конфликтующие имена. Так лучше, если вы оставили такие проблемы из вопроса;) –

ответ

1

Следуя вашему методу расширения, что, если вы определяете ограничение наследования (между узлом и контейнером) на интерфейсе вместо этого и украшаете классы контейнеров интерфейсом.

{ 
    MyNode n = new MyNode(); 
    var c = new MyNode.MyContainer(); 
    c.AddChild(n); 

    MySubNode s = new MySubNode(); 
    c.AddChild(s); 

    OtherNode o = new OtherNode(); 
    o.AddChild(o); 

    //compiler doesn't allow this, as you'd expect: 
    //c.AddChild(o); 
}   

public interface IContainer<TContainerType, TNodeType> 
    where TNodeType : GenericNode<TContainerType, TNodeType> 
    where TContainerType : TNodeType, IContainer<TContainerType, TNodeType> 
{ 
} 

public static class ContainerExtensions 
{ 
    public static void AddChild<TContainerType, TNodeType>(this IContainer<TContainerType, TNodeType> self, TNodeType node) 
     where TNodeType : GenericNode<TContainerType, TNodeType> 
     where TContainerType : TNodeType, IContainer<TContainerType, TNodeType> 
    { 
     GenericNode<TContainerType, TNodeType>.AddChild(self as TContainerType, node); 
    } 
} 

public class GenericNode<TContainerType, TNodeType> 
    where TNodeType : GenericNode<TContainerType, TNodeType> 
    where TContainerType : GenericNode<TContainerType, TNodeType> 
{ 
    TContainerType parent; 
    TNodeType nextNode; 
    TNodeType previousNode; 

    // Only used by Container 
    TNodeType firstChild; 
    TNodeType secondChild; 

    internal static void AddChild(TContainerType container, TNodeType node) 
    { 
     container.firstChild = node; 
     node.parent = container; 
    } 
} 

public class MyNode : GenericNode<MyContainer, MyNode> 
{   
} 

public class MyContainer : MyNode, IContainer<MyContainer, MyNode> 
{ 
} 

public class MySubNode : MyNode 
{ 
} 

public class OtherNode : GenericNode<OtherNode, OtherNode>, IContainer<OtherNode, OtherNode> 
{ 
} 
+0

Это приводит к тому, что Контейнеры больше не являются Узлами. –

+0

обновлен с использованием вашего последнего тестового кода –

+0

(Что касается вашего обновленного ответа @ revision 3.) Я боюсь, что я изначально отказался от общедоступных и защищенных свойств и методов, которые для краткости обращаются к этим членам. Вы можете увидеть проблему, которая приводит меня к идее использования дженериков в вашем ответе. То есть, если вы посмотрите на «AddChild», «поддельную» реализацию одного из таких методов - например, можно добавить «OtherNode» к «MyContainer». –

0

Мое решение выглядит следующим образом:

public class Tree<T> : ITree<T> where T : INode{ 
    public T RootNode { get; private set; } 
    public Tree(T rootNode){ 
     RootNode = rootNode; 
    } 
} 

public interface ITree<T> where T : INode{ 
    T RootNode { get; } 
} 

public interface INode{ 
    INode Parent { get; } 
    List<INode> Children { get; } 
} 

internal class Node : INode{ 
    public INode Parent { get; private set; } 
    public List<INode> Children { get; private set; } 
    public Node(INode parent, List<INode> children = new List<INode>()){ 
     Parent = parent; 
     Children = children; 
    } 
} 

HTH.

ПРИМЕЧАНИЕ. Дополнительные проверки, такие как ParentNode! = Null для дочерних узлов; узел принадлежит к тому же самому родителю, к которому он добавляется и т. д., не реализован в этом примере.

+0

Боюсь, это не поможет. Он не решает проблему дифференциации типов, которые могут иметь детей, и типов, которые не могут. (Также, используя «Список», он не является полностью навязчивым.) –

0

(Не делайте этого - оставить его, чтобы помочь предотвратить кого-либо еще уронить расширение случайно тоже;))

ли эта помощь?

public abstract class GenericNode<Node, Container> 
    where Node : GenericNode<Node, Container> 
    where Container : GenericNode<Node, Container>.GenericContainer<Node> 
{ 
    Container parent; 
    Node nextNode; 
    Node previousNode; 

    public abstract class GenericContainer<Branch> where Branch: GenericNode<Node, Container> 
    { 
     private Leaf firstChild; 
     private Leaf secondChild; 
    } 
} 

Также компилируется в 3.5. Branch ограничивается Node заявлением GenericNode.

+1

Uhmn ... в этом случае GenericContainer не является GenericNode, поэтому он больше не является деревом;) –

+1

Контейнер не может расширять узел, поскольку вы указали, что он должен расширять GenericContainer (который, как Я уже упоминал, не является GenericNode). –

+0

Ах, ты прав. Я понимаю что ты имеешь ввиду; Я потерял расширение. Doh. Решение Lean van der Walt является самым близким, когда я возвращаю его. – Lunivore

0

Я думал, что рабочий раствор, но он не полностью работает:

public abstract class GenericNode<Node, Container> 
    where Node : GenericNode<Node, Container> 
    where Container : Node 
{ 
    Container parent; 
    Node nextNode; 
    Node previousNode; 

    // Only used by Container 
    Node firstChild; 
    Node secondChild; 

    public static class ContainerHelpers 
    { 
     public static void AddChild(Container c, Node n) 
     { 
      c.firstChild = n; // not a real implementation ;) 
      n.parent = c; 
     } 
    } 
} 

// EDIT: This does not work correctly! (see example below) 
public static class GenericNodeExtensionMethods 
{ 
    public static void AddChild<Node, Container>(this Container c, Node n) 
     where Node : GenericNode<Node, Container> 
     where Container : Node 
    { 
     GenericNode<Node, Container>.ContainerHelpers.AddChild(c, n); 
    } 
} 

// 
// Example Usage 
// 

public class MyNode : GenericNode<MyNode, MyContainer> 
{ 
} 

public class MyContainer : MyNode 
{ 
} 

public class MySubNode : MyNode 
{ 
} 

public class OtherNode : GenericNode<OtherNode, OtherNode> 
{ 
} 


class Program 
{ 
    static void Main(string[] args) 
    { 
     MyNode n = new MyNode(); 
     MyContainer c = new MyContainer(); 
     c.AddChild(n); 

     MySubNode s = new MySubNode(); 
     // 
     // This does not work because it tries to fill the generic in the 
     // extension method with <MySubNode, MyContainer>, which does not 
     // fulfil the constraint "where Container : Node". 
     // 
     //c.AddChild(s); 

     OtherNode o = new OtherNode(); 
     o.AddChild(o); 
    } 
} 

Хотя метод метод расширения разоблачать методы только для контейнера не работает правильно, структурирование класс GenericNode как это имеет свойство nice, что Container и Node могут быть одного и того же класса, предоставляя конечному пользователю возможность иметь определенный тип в дереве, который может иметь детей, или разрешать всем типам иметь детей.

(также по некоторым причинам метод расширения не отображается в IntelliSense в VC# 2008 SP1, хотя он в 2010 году)

Все еще ищете лучшее решение ...

0

One опция, чтобы оградить клиента от фактической структуры дерева целиком, с помощью не подвергая Узел объекты непосредственно:

public interface ITagged<T> 
{ 
    T Tag { get; set; } 
} 

public sealed class Tree<T> 
{ 
    //All Tree operations are performed here (add nodes, remove nodes, possibly move nodes, etc.) 
    //Nodes are only exposed as 'ITagged<T>', such as: 
    public ITagged<T> Root { get; private set; } 

    public IEnumerable<ITagged<T>> GetChildren(ITagged<T> item) 
    { 
     //Cast to Container and enumerate... 
    } 

    //Several other tree operations... 

    private class Node : ITagged<T> 
    { 
     Container parent; 
     Node nextNode; 
     Node previousNode; 

     public T Tag { get; set; } 
    } 

    private class Container : Node 
    { 
     Node firstChild; 
     Node lastChild; 
    } 
} 

дерево теперь может содержать любой тип объекта данных, без необходимости спускаться из специального типа или включать в себя любые свойства, управляющие tr его структура. Древовидная структура обрабатывается внутренне классом Tree, и все операции дерева выполняются классом Tree. Теперь клиент полностью изолирован от деталей реализации. Все, что когда-либо видел клиент, это их объекты данных. Если вы все еще хотите получать навигацию от узлов, вы можете предоставить ссылку на дерево в интерфейсе узла, а затем предоставить методы расширения, которые используют методы Tree для реализации навигации.

+0

То, что вы описали, похоже, является обычным деревом, а не интрузивным деревом. –

+0

@Andrew. Вы можете использовать этот тип шаблона для реализации любого дерева; это всего лишь способ отслеживания общего контента, изолируя детали реализации дерева. Вы можете легко удалить корень. Другое изменение, которое я сделал, - это перемещение контейнера из узла, поскольку вам больше не понадобится логика внутри узла, но вы все равно можете держать их вложенными, если хотите. –

+1

Точка интрузивного дерева заключается в том, что связи между узлами хранятся в самих узлах (структура дерева «навязчива» в узлах). Это устраняет необходимость сохранения древовидной структуры извне к фактическим данным. Одним из важных последствий этого является то, что манипулирование деревом никогда не приведет к распределению. –

0

Просто исправьте имена параметров общего типа и не забудьте добавить параметр родового типа в унаследованный GenericNode.

ie.

public abstract class GenericNode<TNode, TContainer> 
    where TNode : GenericNode<TNode, TContainer> 
    where TContainer : GenericNode<TNode, TContainer>.GenericContainer 
{ 
    public TContainer Parent { get; set; } 
    public TNode Next { get; set; } 
    public TNode Previous { get; set; } 

    public abstract class GenericContainer : GenericNode<TNode, TContainer> 
    { 
     public TNode FirstChild { get; set; } 
     public TNode LastChild { get; set; } 
    } 
} 

Компилирует просто отлично.

+0

Да, он компилируется отлично. Но это не соответствует целям дизайна. Кроме того, придерживание «T» перед именами типов фактически не делает ничего - они просто имена. Ваш ответ почти идентичен первой попытке Леона ван дер Уолта: http://stackoverflow.com/revisions/2f1874c4-5a26-4607-9f2c-7a7eccbb7d70/view-source –

 Смежные вопросы

  • Нет связанных вопросов^_^