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