Why can't I have abstract static methods in C#?

C#.NetLanguage Design

C# Problem Overview


I've been working with providers a fair bit lately, and I came across an interesting situation where I wanted to have an abstract class that had an abstract static method. I read a few posts on the topic, and it sort of made sense, but is there a nice clear explanation?

C# Solutions


Solution 1 - C#

Static methods are not instantiated as such, they're just available without an object reference.

A call to a static method is done through the class name, not through an object reference, and the Intermediate Language (IL) code to call it will call the abstract method through the name of the class that defined it, not necessarily the name of the class you used.

Let me show an example.

With the following code:

public class A
{
    public static void Test()
    {
    }
}

public class B : A
{
}

If you call B.Test, like this:

class Program
{
    static void Main(string[] args)
    {
        B.Test();
    }
}

Then the actual code inside the Main method is as follows:

.entrypoint
.maxstack 8
L0000: nop 
L0001: call void ConsoleApplication1.A::Test()
L0006: nop 
L0007: ret 

As you can see, the call is made to A.Test, because it was the A class that defined it, and not to B.Test, even though you can write the code that way.

If you had class types, like in Delphi, where you can make a variable referring to a type and not an object, you would have more use for virtual and thus abstract static methods (and also constructors), but they aren't available and thus static calls are non-virtual in .NET.

I realize that the IL designers could allow the code to be compiled to call B.Test, and resolve the call at runtime, but it still wouldn't be virtual, as you would still have to write some kind of class name there.

Virtual methods, and thus abstract ones, are only useful when you're using a variable which, at runtime, can contain many different types of objects, and you thus want to call the right method for the current object you have in the variable. With static methods you need to go through a class name anyway, so the exact method to call is known at compile time because it can't and won't change.

Thus, virtual/abstract static methods are not available in .NET.

Solution 2 - C#

Static methods cannot be inherited or overridden, and that is why they can't be abstract. Since static methods are defined on the type, not the instance, of a class, they must be called explicitly on that type. So when you want to call a method on a child class, you need to use its name to call it. This makes inheritance irrelevant.

Assume you could, for a moment, inherit static methods. Imagine this scenario:

public static class Base
{
    public static virtual int GetNumber() { return 5; }
}

public static class Child1 : Base
{
    public static override int GetNumber() { return 1; }
}

public static class Child2 : Base
{
    public static override int GetNumber() { return 2; }
}

If you call Base.GetNumber(), which method would be called? Which value returned? It's pretty easy to see that without creating instances of objects, inheritance is rather hard. Abstract methods without inheritance are just methods that don't have a body, so can't be called.

Solution 3 - C#

Another respondent (McDowell) said that polymorphism only works for object instances. That should be qualified; there are languages that do treat classes as instances of a "Class" or "Metaclass" type. These languages do support polymorphism for both instance and class (static) methods.

C#, like Java and C++ before it, is not such a language; the static keyword is used explicitly to denote that the method is statically-bound rather than dynamic/virtual.

Solution 4 - C#

Here is a situation where there is definitely a need for inheritance for static fields and methods:

abstract class Animal
{
  protected static string[] legs;

  static Animal() {
    legs=new string[0];
  }

  public static void printLegs()
  {
    foreach (string leg in legs) {
      print(leg);
    }
  }
}


class Human: Animal
{
  static Human() {
    legs=new string[] {"left leg", "right leg"};
  }
}


class Dog: Animal
{
  static Dog() {
    legs=new string[] {"left foreleg", "right foreleg", "left hindleg", "right hindleg"};
  }
}


public static void main() {
  Dog.printLegs();
  Human.printLegs();
}


//what is the output?
//does each subclass get its own copy of the array "legs"?

Solution 5 - C#

To add to the previous explanations, static method calls are bound to a specific method at compile-time, which rather rules out polymorphic behavior.

Solution 6 - C#

With .NET 6 / C# 10/next/preview you are able to do exactly that with "Static abstract members in interfaces".

(At the time of writing the code compiles successfully but some IDEs have problems highlighting the code)

SharpLab Demo

using System;

namespace StaticAbstractTesting
{
    public interface ISomeAbstractInterface
    {
        public abstract static string CallMe();
    }

    public class MyClassA : ISomeAbstractInterface
    {
        static string ISomeAbstractInterface.CallMe()
        {
            return "You called ClassA";
        }
    }

    public class MyClassB : ISomeAbstractInterface
    {
        static string ISomeAbstractInterface.CallMe()
        {
            return "You called ClassB";
        }
    }

    public class Program
    {

        public static void Main(string[] args)
        {
            UseStaticClassMethod<MyClassA>();
            UseStaticClassMethod<MyClassB>();
        }

        public static void UseStaticClassMethod<T>() where T : ISomeAbstractInterface
        {
            Console.WriteLine($"{typeof(T).Name}.CallMe() result: {T.CallMe()}");
        }
    }
}

Since this is a major change in the runtime, the resulting IL code also looks really clean, which means that this is not just syntactic sugar.

public static void UseStaticClassMethodSimple<T>() where T : ISomeAbstractInterface {
IL_0000: constrained. !!T
IL_0006: call string StaticAbstractTesting.ISomeAbstractInterface::CallMe()
IL_000b: call void [System.Console]System.Console::WriteLine(string)
IL_0010: ret
}

Resources:

Solution 7 - C#

We actually override static methods (in delphi), it's a bit ugly, but it works just fine for our needs.

We use it so the classes can have a list of their available objects without the class instance, for example, we have a method that looks like this:

class function AvailableObjects: string; override;
begin
  Result := 'Object1, Object2';
end; 

It's ugly but necessary, this way we can instantiate just what is needed, instead of having all the classes instantianted just to search for the available objects.

This was a simple example, but the application itself is a client-server application which has all the classes available in just one server, and multiple different clients which might not need everything the server has and will never need an object instance.

So this is much easier to maintain than having one different server application for each client.

Hope the example was clear.

Solution 8 - C#

This question is 12 years old but it still needs to be given a better answer. As few noted in the comments and contrarily to what all answers pretend it would certainly make sense to have static abstract methods in C#. As philosopher Daniel Dennett put it, a failure of imagination is not an insight into necessity. There is a common mistake in not realizing that C# is not only an OOP language. A pure OOP perspective on a given concept leads to a restricted and in the current case misguided examination. Polymorphism is not only about subtying polymorphism: it also includes parametric polymorphism (aka generic programming) and C# has been supporting this for a long time now. Within this additional paradigm, abstract classes (and most types) are not only used to provide a type to instances. They can also be used as bounds for generic parameters; something that has been understood by users of certain languages (like for example Haskell, but also more recently Scala, Rust or Swift) for years.

In this context you may want to do something like this:

void Catch<TAnimal>() where TAnimal : Animal
{
	string scientificName = TAnimal.ScientificName; // abstract static property
	Console.WriteLine($"Let's catch some {scientificName}");
	…
}

And here the capacity to express static members that can be specialized by subclasses totally makes sense!

Unfortunately C# does not allow abstract static members but I'd like to propose a pattern that can emulate them reasonably well. This pattern is not perfect (it imposes some restrictions on inheritance) but as far as I can tell it is typesafe.

The main idea is to associate an abstract companion class (here SpeciesFor<TAnimal>) to the one that should contain static abstract members (here Animal):

public abstract class SpeciesFor<TAnimal> where TAnimal : Animal
{
	public static SpeciesFor<TAnimal> Instance { get { … } }

	// abstract "static" members

	public abstract string ScientificName { get; }
	
	…
}

public abstract class Animal { … }

Now we would like to make this work:

void Catch<TAnimal>() where TAnimal : Animal
{
	string scientificName = SpeciesFor<TAnimal>.Instance.ScientificName;
	Console.WriteLine($"Let's catch some {scientificName}");
	…
}

Of course we have two problems to solve:

  1. How do we make sure an implementer of a subclass of Animal provides a specific instance of SpeciesFor<TAnimal> to this subclass?
  2. How does the property SpeciesFor<TAnimal>.Instance retrieve this information?

Here is how we can solve 1:

public abstract class Animal<TSelf> where TSelf : Animal<TSelf>
{
	private Animal() {}
	
	public abstract class OfSpecies<TSpecies> : Animal<TSelf>
		where TSpecies : SpeciesFor<TSelf>, new()
	{
		protected OfSpecies() : base() { }
	}
	
	…
}

By making the constructor of Animal<TSelf> private we make sure that all its subclasses are also subclasses of inner class Animal<TSelf>.OfSpecies<TSpecies>. So these subclasses must specify a TSpecies type that has a new() bound.

For 2 we can provide the following implementation:

public abstract class SpeciesFor<TAnimal> where TAnimal : Animal<TAnimal>
{
	private static SpeciesFor<TAnimal> _instance;

	public static SpeciesFor<TAnimal> Instance => _instance ??= MakeInstance();

	private static SpeciesFor<TAnimal> MakeInstance()
	{
		Type t = typeof(TAnimal);
		while (true)
		{
			if (t.IsConstructedGenericType
					&& t.GetGenericTypeDefinition() == typeof(Animal<>.OfSpecies<>))
				return (SpeciesFor<TAnimal>)Activator.CreateInstance(t.GenericTypeArguments[1]);
			t = t.BaseType;
			if (t == null)
				throw new InvalidProgramException();
		}
	}

    // abstract "static" members

    public abstract string ScientificName { get; }
    
    …
}

How do we know that the reflection code inside MakeInstance() never throws? As we've already said, almost all classes within the hierarchy of Animal<TSelf> are also subclasses of Animal<TSelf>.OfSpecies<TSpecies>. So we know that for these classes a specific TSpecies must be provided. This type is also necessarily constructible thanks to constraint : new(). But this still leaves out abstract types like Animal<Something> that have no associated species. Now we can convince ourself that the curiously recurring template pattern where TAnimal : Animal<TAnimal> makes it impossible to write SpeciesFor<Animal<Something>>.Instance as type Animal<Something> is never a subtype of Animal<Animal<Something>>.

Et voilà:

public class CatSpecies : SpeciesFor<Cat>
{
    // overriden "static" members

	public override string ScientificName => "Felis catus";
	public override Cat CreateInVivoFromDnaTrappedInAmber() { … }
	public override Cat Clone(Cat a) { … }
	public override Cat Breed(Cat a1, Cat a2) { … }
}

public class Cat : Animal<Cat>.OfSpecies<CatSpecies>
{
    // overriden members

	public override string CuteName { get { … } }
}

public class DogSpecies : SpeciesFor<Dog>
{
    // overriden "static" members

	public override string ScientificName => "Canis lupus familiaris";
	public override Dog CreateInVivoFromDnaTrappedInAmber() { … }
	public override Dog Clone(Dog a) { … }
	public override Dog Breed(Dog a1, Dog a2) { … }
}

public class Dog : Animal<Dog>.OfSpecies<DogSpecies>
{
    // overriden members

	public override string CuteName { get { … } }
}

public class Program
{
	public static void Main()
	{
		ConductCrazyScientificExperimentsWith<Cat>();
		ConductCrazyScientificExperimentsWith<Dog>();
		ConductCrazyScientificExperimentsWith<Tyranosaurus>();
		ConductCrazyScientificExperimentsWith<Wyvern>();
	}
	
	public static void ConductCrazyScientificExperimentsWith<TAnimal>()
		where TAnimal : Animal<TAnimal>
	{
		// Look Ma! No animal instance polymorphism!
		
		TAnimal a2039 = SpeciesFor<TAnimal>.Instance.CreateInVivoFromDnaTrappedInAmber();
		TAnimal a2988 = SpeciesFor<TAnimal>.Instance.CreateInVivoFromDnaTrappedInAmber();
		TAnimal a0400 = SpeciesFor<TAnimal>.Instance.Clone(a2988);
		TAnimal a9477 = SpeciesFor<TAnimal>.Instance.Breed(a0400, a2039);
		TAnimal a9404 = SpeciesFor<TAnimal>.Instance.Breed(a2988, a9477);
		
		Console.WriteLine(
			"The confederation of mad scientists is happy to announce the birth " +
			$"of {a9404.CuteName}, our new {SpeciesFor<TAnimal>.Instance.ScientificName}.");
	}
}

A limitation of this pattern is that it is not possible (as far as I can tell) to extend the class hierarchy in a satifying manner. For example we cannot introduce an intermediary Mammal class associated to a MammalClass companion. Another is that it does not work for static members in interfaces which would be more flexible than abstract classes.

Solution 9 - C#

The abstract methods are implicitly virtual. Abstract methods require an instance, but static methods do not have an instance. So, you can have a static method in an abstract class, it just cannot be static abstract (or abstract static).

Solution 10 - C#

It's available in C# 10 as a preview feature for now.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionlomaxxView Question on Stackoverflow
Solution 1 - C#Lasse V. KarlsenView Answer on Stackoverflow
Solution 2 - C#David WengierView Answer on Stackoverflow
Solution 3 - C#Chris HansonView Answer on Stackoverflow
Solution 4 - C#user275801View Answer on Stackoverflow
Solution 5 - C#RytmisView Answer on Stackoverflow
Solution 6 - C#0BLUView Answer on Stackoverflow
Solution 7 - C#Fabio GomesView Answer on Stackoverflow
Solution 8 - C#BurakuminView Answer on Stackoverflow
Solution 9 - C#AMingView Answer on Stackoverflow
Solution 10 - C#GeniusbabyView Answer on Stackoverflow