【问题标题】:Member with same type as class instance与类实例具有相同类型的成员
【发布时间】:2013-07-25 22:22:54
【问题描述】:

正如标题所说,我想知道是否可能。

我有一个节点类指向同一数据结构中的另一个节点。

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

我希望能够扩展 Node 类,以便获得有关 DataStructure 的某些通用版本的更多信息。例如:

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

问题是当我尝试关注NextNode 链接时。

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

另外,我想让它不可能做这样的事情:

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!

有没有办法在构建时强制Node.NextNodethis 的类型相同?我希望能够实现通用数据结构而无需进行强制转换。是否可以?我使用的是劣质设计模式吗?

【问题讨论】:

  • 我认为这是*.com/questions/1327568/…Curiously recurring template pattern 上的其他主题的重复。
  • 您错过了向我们提供示例中使用的非通用AdvancedNode 的定义..
  • @KenKin 没有非泛型 AdvancedNode(据我所知,该类型是从其他类型参数推断出来的)
  • @KenKin 将其更改为 DataStructure> 更清楚。
  • @Curtor:我猜你要实现的就像LinkedListNode<T>

标签: c# inheritance data-structures types


【解决方案1】:

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

我们将Node&lt;T&gt; 的定义更改为Node&lt;N, T&gt;,其中N 必须实现Node&lt;N, T&gt;...

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

然后,您只需将AdvancedNode&lt;T&gt; 的基类更改为Node&lt;AdvancedNode&lt;T&gt;, T&gt;

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

以及DataStructure&lt;T, N&gt;Node&lt;N, T&gt;中对类型参数N的约束。

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

    // More code to follow, etc.
}

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

class SimpleNode<T> : Node<SimpleNode<T>, T>
    where T : IComparable<T>
{
}

【讨论】:

  • 这与我尝试获得它的结果差不多,但不幸的是,当您想要返回它作为结果时,仍然需要进行类型转换。
  • 你在哪里需要演员表?在Node&lt;N, T&gt; 中(例如,N current = (N)this;)?如果是你所说的演员。我没有看到任何避免它的解决方案。但也常说(这也必须继承自N:这就是我把Node&lt;N, T&gt;抽象成抽象的原因。不要有必须禁止的奇怪结构Node&lt;AdvancedNode&lt;int&gt;, int&gt;)。
  • 对,这就是我想问的——是否可以在不强制转换的情况下做到这一点。我知道这是一个安全的演员阵容,但很好奇我是否错过了绕过它的方法。这也意味着如果您想拥有无限的继承链,您需要对 AdvancedNode 进行相同的递归定义 - SpecialNode : AdvancedNode, T>
  • 我不得不承认,这已经达到了我的知识极限! 是否可以在不进行强制转换的情况下做到这一点? 我还没有找到解决方案,但可以为 Node.js 使用不同的结构。 这也意味着如果你想拥有无限的继承链,你需要用AdvancedNode做同样的递归定义。在这种情况下,是的。但同样,也许还有其他我不知道的解决方案。
  • 我认为我的回答在技术上与您的意义相同,并且没有解决方案可以摆脱像 N current = (N)this; 这样的东西,因为类不能从类型参数继承。跨度>
【解决方案2】:

我没有 VS,但在我看来你应该:

  1. Node&lt;T&gt; 更改为Node&lt;TData,TNode&gt; 并将NextNode 声明为Node&lt;TData,TNode&gt;
  2. AdvancedNode 声明为AdvancedNode&lt;TData&gt; : Node&lt;TData,AdvancedNode&lt;TData&gt;&gt;

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

  1. RootNode 属性移动到Node(它似乎比数据更重要)。
  2. DataStructure&lt;T,N&gt; 更改为DataStructure&lt;TNode&gt;(如果需要,从TNode 推断TData

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

【讨论】:

  • 拥有一个包装 DataStructure 允许对单个节点进行受限访问;例如,如果您想跟踪每次添加节点的时间,并通过 O(1) 调用来获取节点数。第一点与 Cédric Bignon 的回答类似,但是当您想在 GetLast 方法中返回它时仍然存在问题。
  • @Curtor 做了一些更正,但需要 VS 来处理细节;)您是否考虑过使用访问者模式而不是包装?也就是说,将节点传递给 DataStructure 进行操作,而不是将其作为类/类型/构造函数依赖项。
  • 这不是一个坏主意,感谢您提供替代模式建议。我将不得不对此进行试验。
【解决方案3】:

根据您的代码,唯一知道N 的类是DataStructure&lt;T, N&gt;。在下面的阐述中,我使用TValue 代替T,并使用TNode 代替N

你的期望是:

  1. 约束传递给DataStructure&lt;TValue, TNode&gt;的节点类型;这样一个节点的NextNode 也属于同一类型。

  2. 无需额外转换

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

由于IComparable&lt;T&gt;只是值的约束,与你的问题无关,我在下面的代码中把它去掉,使代码更清晰易懂,如果你需要的话,把它加回来。

  • 代码

    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(this.NextNode!=null) { current=current.NextNode; } 中的while 循环表达式会导致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&lt;T&gt;是引用类型,==是用来比较引用的,结果符合预期。

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

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

【讨论】:

  • 为什么会 while(this.NextNode!=null) { current=current.NextNode;导致 NullReferenceException? this 不能为空,并且在检查 NextNode 之前不要取消引用它。您对 GetLastNode() 的实现基本上做了同样的事情。
  • 您的答案与 Cédric Bignon 的答案基本相同,您可以在其中约束 NextNode 指针,但仍需要强制转换才能返回。 var current=this as TNode 本质上与进行强制转换相同。我不确定将 Node 类放在 DataStructure 类中会带来什么好处。这个问题与我正在尝试做的事情相比非常简化(因此问题简洁明了),因此为什么我不只使用 LinkedList.
  • @Curtor:您检查了this 不为空,但使用current 进行分配。下次循环时,current 可以为 null,current.NextNode 会导致 NRE。
【解决方案4】:

另一种选择可能是指定一个组合来反对使用继承。这样做的缺点是扩展特定组合会更加困难。

例如,使用问题中的类:

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;

【讨论】: