On Static Classes In C#

On Static Classes In C#

C# is a powerful high-level programming language that has tons of features.
One of those features is static classes.

In this article, I will provide my own thoughts and feelings on the feature.

What are static classes?

Static classes are classes that:

  • Contain only static members - fields and properties, methods, or inner classes
  • Can not be instantiated
  • Can not be extended nor implement interfaces

The fact that there can be no instances of static classes imposes an additional property which, to me personally, is the most interesting one:

  • Static classes can not be passed as arguments to constructors and methods, and they can not be returned from methods

Static classes are merely containers/holders for a certain set of functions.

In C# they are commonly used to define extension methods - another thing that I am not a big fan of and will try to explain why in my next article.

We would ideally use a static class in scenarios where we have a class without state - only behavior.
If the state exists, however, then it should be constant.
The behavior should be represented as a set of pure functions(methods).
Pure functions are functions that, given the same input, always return the same output and leave the system in the same state - they produce no side effects.

A good example of a static class in C# is the System.Math static class.

Take a look at the class below.

public static class WeightConverter
{
  public static double KilogramToPound(double weightInKilograms)
  {
     return weightInKilograms * 2.20462262;
  }

  public static double PoundToKilogram(double weightInPounds)
  {
      return weightInPounds * 0.45359237;
  }
}

This a valid case for a static class.

We do not really need objects of this class, there is no internal state, and the methods are pure.

Static classes represent, in a way, a compromise between object-oriented design on one side, and convenience and simplicity on the other side.

This is one of the reasons why we should use them sparingly.

Overuse of static classes can drag us in the wrong direction and ruin the object-oriented design of our system.

They should exist only as a supporting programming unit for the rest of the system which is designed in an object-oriented way.

Another reason why we should use static classes sparingly is the way they tend to get treated.

At some point in development, static classes start to get treated as a container for everything.
Sooner or later they become containers for a bunch of unrelated functionalities - ending up as the famous utility or helper classes.

These static "utility" classes have low cohesion and increase the complexity of the design.

What is cohesion?

Cohesion, together with coupling, is a metric that is used to evaluate the complexity of our software design.

Cohesion is the metric that focuses on the complexity inside of a module.
For the purpose of this article think of a module as a class.
It is somehow related to the first of five SOLID principles - the Single Responsibility Principle.
Cohesion can be high or low.

Cohesion answers the question of how clear the purpose of a module is.
Is the module doing one thing? In other words, does it have a single responsibility?
If the answer is yes then the module has high cohesion.

On the contrary, if it is unclear what the purpose of a module is, or if it has many responsibilities then the module has low cohesion.

Good software design consists of modules that have high cohesion.
Bad software design consists of modules that have low cohesion.

Coupling is the metric that focuses on the complexity between modules.
If a module relies too much on other modules then it has high coupling - it is tightly coupled to those modules.
If a module connects easily to other modules, through well-defined interfaces, then it has low coupling - it is loosely coupled to those modules.

Good software design consists of modules that are loosely coupled.
Bad software design consists of modules that are tightly coupled.

The most desirable scenario is for the design to have high cohesion and loose coupling.

Back to static classes.

How do cohesion and coupling relate to how I feel about static classes in C#?

As I already said, static classes tend to become containers for a bunch of unrelated functionalities - ending up as the famous utility or helper classes.
When a static "utility" class contains a lot of unrelated functionalities it becomes unclear what its purpose is and it does not have a single responsibility.
This means that this module, that is, our static utility class, has low cohesion which not a desirable characteristic.

What about coupling?

If some class uses a functionality from a static class then it is coupled to it - which is OK.

But here is how I see it.

I called this implicit coupling.

A class that is coupled to a static class is coupled to it in an implicit way.
When a class relies, that is, depends, on another class, it should be explicit about that dependency.

There is an architectural principle called the Explicit Dependencies Principle created by Steve Smith, a.k.a Ardalis.
The principle says that a class should explicitly require the dependencies that it needs in order to function correctly and/or to be in a proper state.

How do we do that?

We add the dependency in the constructor.
The constructor should tell the client the exact dependencies which the class needs in order to function correctly.

The class is then more honest to the clients.

This makes sense to me - by looking at the public API of a class, you can create an idea of what the class is doing - you create a more self-documenting code.

If a class depends on a static class then we should make that dependency explicit by stating it in the constructor.

But static classes can not be passed as arguments to constructors.

The fundamental theorem of software engineering says:

We can solve any problem by introducing an extra level of indirection.

The result of how I interpreted the sentence above is the following idea:

We could wrap the static class inside a regular class which we then can register as a singleton in the IoC container.
We can then pass this object in the constructor as a dependency.

This is something that I would do with static classes that we defined and that are existent in our system, but we want to make them an explicit dependency in some places.
I would not do something like this with static classes that the framework/language provides, those static classes should be used as usual.

In case that we are about to write a new static class, but have the Explicit Dependencies Principle in the mind, we might as well simply use a non-static singleton class.

Even before reading about the Explicit Dependencies Principle, it somehow felt wrong to me when a class "hiddenly" depends on something outside of its context.

My final thoughts are that I am just a bit skeptical about static classes and that we should use them sparingly.
There are cases where it makes sense to use one.

Make sure to follow a couple of guidance points when there exists a decision to use a static class:

  • All methods should be pure
  • The class should ideally not contain any state, but if it does the state should be constant
  • Make the class specialized, do not create a "utility" class


Feel free to let me know in the comments what your thoughts and experiences with static classes are, and point out any mistakes.