-2

Suppose I have an abstract base class that I want to declare members in that will match the type of the classes that derive from this base class.

public abstract class BaseClass
{
    protected BaseClass parent;
}

public class DerivedClass1 : BaseClass
{
    // parent could be of type DerivedClass2
}

public class DerivedClass2 : BaseClass
{
    // parent could be of type DerivedClass1
}

This won't work because the parent field in each derived class can be anything that derives from BaseClass. I want to ensure that the parent field in DerivedClass1 can only be of type DerivedClass1. So I'm thinking maybe I should use generics.

public abstract class BaseClass<T> where T : BaseClass<T>
{
    protected T parent;
}

This may seem confusing and circular, but it does compile. It's basically saying parent is of type T which has to derive from the generic BaseClass. So now a derived class can look like this:

public class DerivedClass : BaseClass<DerivedClass>
{
    // parent is of type DerivedClass
}

The problem is that I have to enforce the type-matching myself when I declare DerivedClass. There's nothing stopping someone from doing something like this:

public class DerivedClass1 : BaseClass<DerivedClass2>
{
    // parent is of type DerivedClass2
}

Does C# have a way of doing this so that the type of a member declared in the base is sure to match the derived type?

I think this is similar to what this C++ question was trying to ask: Abstract base class for derived classes with functions that have a return type of the derived class

Kyle Delaney
  • 11,616
  • 6
  • 39
  • 66
  • 2
    This is called the CRTP. – SLaks Nov 14 '17 at 03:00
  • If you want the parent property to be of specific type then why you need to worry about which base class that type is inheriting from? You also don't need to have this property declared in abstract class. You just need to create property in appropriate classes. – Chetan Nov 14 '17 at 03:05
  • I'm afraid your question is unclear. I mean, it's clear enough what you want. But, you should have found out by now C# doesn't provide that kind of constraint. More to the point, your question offers zero detail regarding _why_ you think this constraint would be useful. Why does it matter to you if the type parameter `T` isn't in fact the same as the declaring type? How would that affect the implementation of the base class or the derived class? What wouldn't you be able to do, that you could with such a constraint? Please provide more details about your expectations. – Peter Duniho Nov 14 '17 at 03:14
  • The answer to your question is "No". – Enigmativity Nov 14 '17 at 06:23
  • I was afraid that including more specifics about the program I'm trying to create would double the length of my already long question. – Kyle Delaney Nov 14 '17 at 16:02

1 Answers1

1

If I understand your requirements correctly, you have a series of classes with an inheritance relationship, and you wish to arrange them in a tree structure where each instance has a parent of the same type and only of the same type. It's an interesting problem.

After noodling with this for a bit, may I suggest you separate the requirements into two parallel but related object graphs, so that you have

  1. A set of classes with an inheritance relationship
  2. A set of classes that can contain any of the classes from the first set, and have type-strict parents and children.

First, let's declare the first set of classes that inherit from each other. Ignore the Node bit for now.

public class BaseClass 
{
    public Node ContainerNode { get; set; }
}

public class DerivedClass1 : BaseClass
{
}

public class DerivedClass2 : BaseClass
{
}

These classes can't do much but it's just an example.

Now let's set up another set of classes that can participate in a tree. Each element in the tree is called a Node.

//Basic node
public class Node
{
}

//A node that can contain a T (which must be a BaseClass or derived from one)
public class Node<T> : Node where T : BaseClass
{
    public T Parent { get; set; }
    public T This { get; set; }

    public Node(T innerClass) 
    {
        this.This = innerClass;
        innerClass.ContainerNode = this;
    }
}

Now we have everything we need to enforce the type safety you seek. We can create classes in your inheritance hierarchy like this:

    var child1 = new Node<DerivedClass1>(new DerivedClass1());
    var parent1 = new Node<DerivedClass1>(new DerivedClass1());
    child1.Parent = parent1.This;

Let's see what happens if we mistakenly mix up DerivedClass1 and DerivedClass2:

    var child2 = new Node<DerivedClass2>(new DerivedClass2());
    var parent2 = new Node<DerivedClass1>(new DerivedClass1()); //Oops
    child2.Parent = parent2.This;  //Does not compile

So as you can see, the Parent property is now typesafe.

Now all that stuff ^^^^ looks kind of messy, so let's clean it up by adding a few helper methods.

public class Node
{
    public T GetParent<T>() where T : BaseClass
    {
        return ((Node<T>)this).Parent;
    }

    static public Node<T> Create<T>(T innerClass) where T : BaseClass
    {
        return new Node<T>(innerClass);
    }

    static public T GetParent<T>(T child) where T: BaseClass
    {
        return child.ContainerNode.GetParent<T>();
    }

    static public implicit operator T (Node<T> input)
    {
        return input.This;
    }
}

Now, since the compiler can infer the <T> arguments, our declarations are much neater:

    var child1 = Node.Create(new DerivedClass1());
    var parent1 = Node.Create(new DerivedClass1());
    child1.Parent = parent1;

And it's easy for any of the derived classes to find its own parent:

public class DerivedClass1 : BaseClass
{
    protected DerivedClass1 Parent
    {
        get
        {
            return Node.GetParent(this);  //This is type safe!
        }
    }
}

One objection to all this is that you don't want coders to deal with this Node layer. Well, they don't, because we set up an implicit cast:

    Node<DerivedClass1> a = Node.Create(new DerivedClass1());
    DerivedClass1 b = a;  //Works!!! And is type-safe.

Full working code on DotNetFiddle.

John Wu
  • 50,556
  • 8
  • 44
  • 80
  • Very impressive! It's worrying that members like `ContainerNode` and `Parent` and `This` are public, but perhaps they can be internal. – Kyle Delaney Nov 14 '17 at 17:29
  • 1
    Agreed. Or you could make them `public readonly` and set them via constructor arguments. If they're immutable it's not as huge a concern. – John Wu Nov 14 '17 at 18:23