29

Is it possible to define a generic type in C# that references itself?

E.g. I want to define a Dictionary<> that holds its type as TValue (for a hierarchy).

Dictionary<string, Dictionary<string, Dictionary<string, [...]>>>
laktak
  • 57,064
  • 17
  • 134
  • 164
  • No, this is not possible. Could you be more specific at what you are trying to achieve? – Darin Dimitrov Mar 15 '09 at 09:22
  • lol Earwicker, you gotta agree it is weird ;) ... I also thought it didn't (directly) .. – eglasius Mar 15 '09 at 09:31
  • I think people get confused because a class cannot inherit from itself (obviously, or it would have infinite size as soon as it had any fields), nor can a generic inherit from a type parameter, but the class's own name and type parameters may appear in the type arguments of a generic base just fine. – Daniel Earwicker Mar 15 '09 at 09:37

2 Answers2

58

Try:

class StringToDictionary : Dictionary<string, StringToDictionary> { }

Then you can write:

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

General principle for recursion: find some way to give a name to the recursive pattern, so it can refer to itself by name.

bugmagnet
  • 7,631
  • 8
  • 69
  • 131
Daniel Earwicker
  • 114,894
  • 38
  • 205
  • 284
  • 1
    Your code requires the generic type to be subclassable - if not we could use `using` to define an alias, but unfortunately you cannot use `using` with open generic types - which also means we can’t define _Curried_ or partial-applications of open generic types. It’s a maddening limitation of C#. – Dai Jan 02 '21 at 22:30
18

Another example would be generic tree

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

Now to use it

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
}

Note that you cannot directly instantiate Tree<TDerived>. It has to be a base class to the node class in the tree. Think class Node : Tree<Node> { }.

John Alexiou
  • 28,472
  • 11
  • 77
  • 133
  • I don't quite understand the snippet. If you're going to be using a derived type of `Tree` why even bother with the where constraint in `Tree where TDerived : Tree` ? In your main code you're only using the derived class, so for what purpose is there the `where` constraint in the `Tree` declaration? – John P Jul 26 '20 at 23:43
  • To ensure the inherited type returns the correct type when implementing the base properties such as `TDerived Parent { get; }` which is automatically satisfied without having the explicitly declare a `CoordSys Parent { get; }` in the derived class. It is all the code that is not there that is important. – John Alexiou Jul 27 '20 at 00:18
  • @JohnP - In my example it says that a `CoordSys` is a `Tree`. The type is the data structure and the data itself, instead of having a `Node` being the data structure and `T` the data. Using a recursive generic type simplifies a lot of code, and the generic constraint gives powers to `T` beyond `object` making it self-aware of its methods and properties. – John Alexiou Jul 27 '20 at 01:48
  • I understand the recursive `CoordSys : Tree` declaration and what it does - it's a pretty neat way of ensuring some type of "self" reference, so it's useful for declarations of various linked-lists/structures that contain a reference/pointer to an object of their own type. And I understand the generic `Tree` actually declares what these references are supposed to be. But what would the issue would be if in your code we removed `where TDerived : Tree` in the `Tree` declaration - wouldn't everything work perfectly fine without it in your code? – John P Jul 27 '20 at 17:28
  • I can see that `where` in `public class Tree where TDerived : Tree` might be useful, if for example we want to declare some other `SpecialList : Tree` class - the `where` in the `Tree` class would ensure that we can only use `SpecialList` for types `TDerived` that inherit from `Tree`. But in your example, this doesn't happen anywhere, and neither is there a `Tree` being instantiated anywhere, so I don't get how the `where` changes any functionality or ensures any behavior. – John P Jul 27 '20 at 17:35