递归泛型类型

29

在C#中是否可以定义一个引用自身的泛型类型?

例如,我想定义一个Dictionary<>,它将其类型作为TValue(用于层次结构)。

Dictionary<string, Dictionary<string, Dictionary<string, [...]>>>

不,这是不可能的。您能更具体地说明您想要实现什么吗? - Darin Dimitrov
lol Earwicker,你得承认这很奇怪;)...我也觉得它(直接地)没有。 - eglasius
我认为人们会感到困惑,因为一个类不能从自身继承(显然,否则它一旦有任何字段就会具有无限大小),也不能从类型参数继承,但是类的自身名称和类型参数可以出现在泛型基类的类型参数中。 - Daniel Earwicker
2个回答

58

尝试:

class StringToDictionary : Dictionary<string, StringToDictionary> { }

然后你可以写:

var stuff = new StringToDictionary
        {
            { "Fruit", new StringToDictionary
                {
                    { "Apple", null },
                    { "Banana", null },
                    { "Lemon", new StringToDictionary { { "Sharp", null } } }
                }
            },
        };

递归的一般原则是:找到一种方式,给递归模式取一个名字,这样它就可以通过名称引用自身。


1
你的代码要求泛型类型可被子类化,否则我们可以使用 using 定义别名,但不幸的是,你不能在开放式泛型类型中使用 using,这也意味着我们无法定义开放式泛型类型的 Curried 或部分应用。这是 C# 的一个令人烦恼的限制。 - Dai

19

另一个例子是通用树

public class Tree<TDerived> where TDerived : Tree<TDerived>
{
    public TDerived Parent { get; private set; }
    public List<TDerived> Children { get; private set; }
    public Tree(TDerived parent)
    {
        this.Parent = parent;
        this.Children = new List<TDerived>();
        if(parent!=null) { parent.Children.Add(this); }
    }
    public bool IsRoot { get { return Parent == null; } }
    public bool IsLeaf { get { return Children.Count==0; } }
}

现在开始使用它

public class CoordSys : Tree<CoordSys>
{
    CoordSys() : base(null) { }
    CoordSys(CoordSys parent) : base(parent) { }
    public double LocalPosition { get; set; }
    public double GlobalPosition { get { return IsRoot?LocalPosition:Parent.GlobalPosition+LocalPosition; } }
    public static CoordSys NewRootCoordinate() { return new CoordSys(); }
    public CoordSys NewChildCoordinate(double localPos)
    {
        return new CoordSys(this) { LocalPosition = localPos };
    }
}

static void Main() 
{
    // Make a coordinate tree:
    //
    //                  +--[C:50] 
    // [A:0]---[B:100]--+         
    //                  +--[D:80] 
    //

    var A=CoordSys.NewRootCoordinate();
    var B=A.NewChildCoordinate(100);
    var C=B.NewChildCoordinate(50);
    var D=B.NewChildCoordinate(80);

    Debug.WriteLine(C.GlobalPosition); // 100+50 = 150
    Debug.WriteLine(D.GlobalPosition); // 100+80 = 180
}

请注意,您不能直接实例化Tree<TDerived>。 它必须是树中节点类的基类。考虑使用class Node:Tree<Node> { }


我不太理解这段代码片段。如果您将使用“Tree<TDerived>”的派生类型,为什么还要在“Tree<TDerived> where TDerived: Tree<TDerived>”中添加约束条件?在您的主代码中,您只使用派生类,那么“Tree<TDerived>”声明中的“where”约束条件有什么用途呢? - John P
为确保继承类型在实现基本属性时返回正确的类型,例如TDerived Parent { get; },可以自动满足而无需在派生类中显式声明CoordSys Parent { get; }。重要的是所有未出现的代码。 - John Alexiou
@JohnP - 在我的例子中,它说一个CoordSys是一个Tree<CoordSys>。类型是数据结构和数据本身,而不是有一个Node<T>作为数据结构和T作为数据。使用递归泛型类型简化了很多代码,并且泛型约束赋予了Tobject更强大的能力,使其自我感知其方法和属性。 - John Alexiou
我理解递归的CoordSys : Tree<CoordSys>声明及其功能——这是一种相当不错的方式,可以确保某种类型的“自我”引用,因此对于包含自己类型对象引用/指针的各种链接列表/结构的声明非常有用。而泛型的Tree<CoordSys>则声明了这些引用应该是什么。但如果在你的代码中删除了Tree<TDerived>声明中的where TDerived : Tree<TDerived>,会出现什么问题?没有它的话,一切都能完美地工作吗? - John P
我可以看出,在public class Tree<TDerived> where TDerived : Tree<TDerived>中的where可能很有用,例如,如果我们想要声明一些其他的SpecialList<Tderived> : Tree<TDerived>类 - Tree<TDerived>类中的where将确保我们只能使用继承自Tree<TDerived>的类型TDerived来使用SpecialList。但在您的示例中,这种情况并没有发生,也没有任何地方实例化Tree<TDerived>,因此我不明白where如何改变任何功能或确保任何行为。 - John P

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接