The key is to add a type parameter:
abstract class Base<T>
where T : Base<T>
{
public static string GetClassName()
{
return typeof(T).Name;
}
}
class A : Base<A> { }
class B : Base<B> { }
This makes static members of Base
belong to the particular class Base<T>
, whatever T
is. So there's no longer a single Base.GetClassName
method, but in this case there are now two methods: Base<A>.GetClassName
and Base<B>.GetClassName
. Additionally, the constraint on the type parameter makes it impossible to have class B : Base<A>
, which guarantees that A.GetClassName
and B.GetClassName
will return different things.
Behavior specific to child classes can be extended even further:
abstract class Base<T>
where T : Base<T>
{
private static HashSet<string> values = new HashSet<string>();
internal Base(string value)
{
if (Base<T>.values.Contains(value))
throw new Exception("Not unique");
else
Base<T>.values.Add(value);
}
public static string GetClassName()
{
return typeof(T).Name;
}
public static IEnumerable<string> GetValues()
{
return new LinkedList<string>(Base<T>.values);
}
}
class A : Base<A>
{
public A(string value) : base(value) { }
}
class B : Base<B>
{
public B(string value) : base(value) { }
}
static void Main(string[] args)
{
var a1 = new A("value");
var a2 = new A("value 2");
// var a3 = new A("value"); // Would throw an exception
var b = new B("value"); // Does not throw an exception
Console.WriteLine(A.GetClassName()); // Prints "A"
Console.WriteLine(B.GetClassName()); // Prints "B"
Console.WriteLine("The values in A:");
foreach (var value in A.GetValues()) // This loop prints "value" and "value 2"
{
Console.WriteLine("\t" + value);
}
Console.WriteLine("The values in B:");
foreach (var value in B.GetValues()) // This loop prints "value"
{
Console.WriteLine("\t" + value);
}
}
In this case, there are different static values
objects in Base<A>
/A
and Base<B>
/B
.
All this comes with a cost, however. In the above example it's impossible for child classes of Base
to access the same static objects without all the child classes knowing of each other a priori. E.g. B
cannot access the same values
object as A
. One possible workaround is to use Base<agreed-upon-type>.values
when you want all child classes to share values.