Как заставить навязчивый класс дерева в C # использовать дженерики? - PullRequest
4 голосов
/ 02 октября 2010

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

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

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

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

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

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

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 и просто буду осторожен в своей собственной библиотеке), я все равно не смогу найти дизайн, который не сталкивается с той же проблемой (и ошибкой).

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

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

Edit: Обязательно посмотрите этот ответ , который является еще одним ударом в дизайне, который пытается использовать методы расширения, чтобы избежать проблемы "внедрения" класса в иерархию наследования ( но, к сожалению, не полностью работает).

Ответы [ 6 ]

1 голос
/ 02 октября 2010

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

{
    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 голосов
/ 03 октября 2010

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

т.

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 голосов
/ 02 октября 2010

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

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;
    }
}

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

0 голосов
/ 02 октября 2010

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

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 выглядит следующим образомобладает приятным свойством, что Container и Node могут быть одним и тем же классом - предоставляя конечному пользователю возможность иметь определенный тип в дереве, которое может иметь дочерние элементы, или разрешать всем типам иметь дочерние элементы.по какой-то причине метод расширения не отображается в IntelliSense в VC # 2008 SP1, хотя в 2010 году он появляется.)

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

0 голосов
/ 02 октября 2010

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

Помогает ли это?

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 ограничивается объявлением GenericNode Node.

0 голосов
/ 02 октября 2010

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

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 для дочерних узлов;узел принадлежит тому же родителю, к которому он добавляется и т. д., который не реализован в этом примере.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...