3

As title says, I'd like to know if it is possible.

I have a node class that points to another node in the same data structure.

class DataStructure<T, N>
  where T : IComparable<T>
  where N : Node<T> {
    N RootNode;

    // More code to follow, etc.
}

class Node<T>
  where T : IComparable<T> {
    T value;
    Node<T> NextNode;

    Node<T> GetLastNode() {
        Node<T> current = this;
        while (this.NextNode != null) {
            current = current.NextNode;
        }
        return current;
    }

    // etc.
}

I want to be able to expand the Node class though, in order to have more information for certain generic version of the DataStructure. For example:

class AdvancedNode<T> : Node<T>
  where T : IComparable<T> {
    int Height;
    int Size;
    // etc.
}

The problem with this is when I try to follow the NextNode link.

DataStructure<char, AdvancedNode<char>> d = new DataStructure<char, AdvancedNode<char>>();
d.RootNode = new AdvancedNode<char>();
d.RootNode.NextNode = new AdvancedNode<char>();
AdvancedNode<char> y = d.RootNode.NextNode;    // TYPE ERROR! Will not compile

Additionally, I want to make it so that it is not possible to do something like this:

DataStructure<char, AdvancedNode<char>> d = new DataStructure<char, AdvancedNode<char>>();
d.RootNode = new AdvancedNode<char>();
d.RootNode.NextNode = new Node<char>();    // This will compile, 
                                           // but I don't want it to!

Is there some way to enforce at build time that Node.NextNode will be the same type as this? I'd like to be able to implement a generic data structure without needing to do casting. Is it possible? Am I using an inferior design pattern?

4

4 回答 4

2

一种可行的解决方案是使用“递归泛型”(参见这篇文章)。

我们将定义更改为Node<T>必须Node<N, T>在哪里N实现Node<N, T>...

abstract class Node<N, T>
    where N : Node<N, T>  // Here is the recursive definition
    where T : IComparable<T>
{
    T value;
    public N NextNode;

    public N GetLastNode()
    {
        N current = (N)this;
        while (this.NextNode != null)
        {
            current = current.NextNode;
        }
        return current;
    }

    // etc.
}

然后,您只需更改 to 的基AdvancedNode<T>Node<AdvancedNode<T>, T>

class AdvancedNode<T> : Node<AdvancedNode<T>, T>
    where T : IComparable<T>
{
    int Height;
    int Size;
    // etc.
}

以及对 to 中类型参数的N约束。DataStructure<T, N>Node<N, T>

class DataStructure<T, N>
    where T : IComparable<T>
    where N : Node<N, T>
{
    public N RootNode;

    // More code to follow, etc.
}

不幸的是,不可能使用“递归泛型”直接实例化一个类,因为它需要编写如下内容:Node<Node<Node<..., T>, T>, T>如果我们想要正确的类型。这就是我把它抽象的原因。为了有一个简单的节点,我创建了一个新类型:

class SimpleNode<T> : Node<SimpleNode<T>, T>
    where T : IComparable<T>
{
}
于 2013-07-25T22:44:50.460 回答
1

我周围没有 VS,但对我来说,你应该:

  1. 更改Node<T>Node<TData,TNode>并将其声明NextNodeNode<TData,TNode>.
  2. 声明AdvancedNodeAdvancedNode<TData> : Node<TData,AdvancedNode<TData>>

这将确保子节点与根节点的类型相同。对所有其他节点类型重复 2)。

  1. RootNode属性移动到Node(它似乎比数据更属于的位置)。
  2. 更改DataStructure<T,N>为(并根据需要DataStructure<TNode>推断)TDataTNode

这将使代码更清晰(在关注点分离方面)并且更易于理解,并且可以帮助您消除让 DataStructure 依赖于节点类型的需要,这将有助于简化事情。让这两种泛型类型相互交叉依赖并不是一个好的设计,所以如果可能的话,我的目标是消除它。

于 2013-07-25T22:47:11.900 回答
1

根据您的代码,唯一知道的类NDataStructure<T, N>. 在下面的详细说明中,我使用TValue代替TTNodefor N

你期待的是:

  1. 约束传递给的节点类型DataStructure<TValue, TNode>;所以NextNode对于一个节点也是相同的类型。

  2. 不需要额外的铸造

那么我能想到的最好的事情就是让节点类型成为一个嵌套的泛型类,你仍然可以约束它的类型参数。

由于IComparable<T>只是值的约束,与您的问题无关,因此我在以下代码中将其删除以使代码清晰易懂,如果需要,只需将其添加回来。

  • 代码

    public partial class DataStructure<TValue, TNode>
            where TNode: DataStructure<TValue, TNode>.Node<TValue> {
        public partial class Node<T> {
            public TNode GetLastNode() {
                var current=this as TNode;
    
                for(; null!=current.NextNode; current=current.NextNode)
                    ;
    
                return current;
            }
    
            public TNode NextNode {
                set;
                get;
            }
    
            public TValue value {
                private set;
                get;
            }
        }
    
        public TNode RootNode;
    }
    
    public partial class AdvancedNode<TValue>
            : DataStructure<TValue, AdvancedNode<TValue>>.Node<TValue> {
        int Height;
        int Size;
    }
    

请注意,原始代码中的 while 循环表达式while(this.NextNode!=null) { current=current.NextNode; }会导致NullReferenceException.

通过上面的代码,现在您可以使用以下测试方法进行测试,该测试方法是原始版本的修改版本:

public static partial class TestClass {
    public static void TestMethod() {
        DataStructure<char, AdvancedNode<char>> d=
            new DataStructure<char, AdvancedNode<char>>();

        d.RootNode=new AdvancedNode<char>();
        d.RootNode.NextNode=new AdvancedNode<char>();

        // type NO error! Will compile
        AdvancedNode<char> y=d.RootNode.NextNode;

        var rootNode=d.RootNode;
        var lastNode=rootNode.GetLastNode();
        Console.WriteLine("Is y the last node? "+(lastNode==y));
        Console.WriteLine("Is rootNode the last node? "+(lastNode==rootNode));
    }
}

由于Node<T>是引用类型,==用于比较引用并且结果符合预期。

您可以对稍后要声明的其他类执行相同的操作。

就个人而言,我会考虑使用LinkedList<T>而不是自己实现它。你在问,所以有答案。

于 2013-07-26T01:52:32.637 回答
0

一种替代方法可能是指定一个反对使用继承的组合。这样做的缺点是扩展特定组合会更加困难。

例如,使用问题中的类:

class DataStructure<T, D>
  where T : IComparable<T> {
    Node<T, D> RootNode;

    // More code to follow, etc.
}

class Node<T, D>
  where T : IComparable<T> {
    T value;
    D data;
    Node<T, D> NextNode;

    Node<T, D> GetLastNode() {
        Node<T, D> current = this;
        while (current .NextNode != null) {
            current = current.NextNode;
        }
        return current;
    }

    // etc.
}

class AdvancedNodeData {
    int Height;
    int Size;
    // etc.
}

DataStructure<char, AdvancedNodeData> d = new DataStructure<char, AdvancedNodeData>();
d.RootNode = new Node<char, AdvancedNodeData>();
d.RootNode.NextNode = new Node<char, AdvancedNodeData>();
Node<char, AdvancedNodeData> y = d.RootNode.NextNode;
于 2013-07-30T16:02:16.920 回答