Freigeben über


How to: override static methods

I know, I know. You can't override static methods. The title was just a trick to provoke your interest :-) In this post, I'll first try to explain why it is impossible to override static methods and then provide two common ways to do it.

Or rather - two ways to achieve the same effect.

So, what's the problem?

There are situations where you wish you could substitute or extend functionality of existing static members - for example, provide different implementations for it and be able to switch implementations at runtime.

For example, let's consider a static class Log with two static methods:

 public static class Log
{
    public static void Message(string message){ ... }
    public static void Error(Exception exception){ ... }
}

Let's say your code calls Log.Message and Log.Error all over the place and you would like to have different logging behaviors - logging to console and to the Debug/Trace listeners. Moreover, you would like to switch logging at runtime based on selected options.

Why can't we override static members?

Really, why? If you think about it, this is just common sense. Overriding usual (instance) members uses the virtual dispatch mechanism to separate the contract from the implementation. The contract is known at compile time (instance member signature), but the implementation is only known at runtime (concrete type of object provides a concrete implementation). You don't know the concrete type of the implementation at compile time.

This is an important thing to understand: when types inherit from other types, they fulfil a common contract, whereas static types are not bound by any contract (from the pure OOP point of view). There's no technical way in the language to tie two static types together with an "inheritance" contract. If you would "override" the Log method in two different places, how do we know which one we are calling here: Log.Message("what is the implementation?")

With static members, you call them by explicitly specifying the type on which they are defined. Which means, you directly call the implementation, which, again, is not bound to any contract.

By the way, that's why static members can't implement interfaces. And that's why virtual dispatch is useless here - all clients directly call the implementation, without any contract.

Let's get back to our problem

I won't even mention the "solution" with if/switch:

 public static void Message(string message)
{
    if (LoggingBehavior == LoggingBehavior.Console)
    {
        Console.WriteLine(message);
    }
    else if ...
}

It is so ugly that my eyes start bleeding when I stare at it long enough. Why?

  1. You cannot add a new type of a log at runtime
  2. You cannot "override" the functionality at runtime or, say, wrap it in a decorator
  3. You have to modify every static method to add/change/remove a logging behavior
  4. all other 10000 reasons why OOP is better than procedural programming

But! We are so close to the first solution - every more or less experienced developer will cry out here: use the Strategy pattern!

Solution 1: Strategy + Singleton

Yes, that's easy. Define a contract to use and it will be automatically separated from the implementation. (Unless you make it sealed. By making a type or a member sealed, you guarantee that no one else can implement this contract.)

OK, so here's our contract:

 public abstract class Logger
{
    public abstract void Message(string message);
    public abstract void Error(Exception exception);
}

You could make it an interface as well, but with an interface you wouldn't be able to change the contract later without breaking existing clients. I already wrote about choosing abstract class vs. interface.

You'll only need one instance of a logging behavior, so let's create a singleton:

 public static class Log
{
    public static Logger Instance { get; set; }
     ...

Correct Singleton implementation is not part of this discussion - there is a whole science of how to correctly implement Singleton in .NET - just use your favorite search engine if you're curious.

Now we just redirect the static methods to instance methods of the Logger instance and presto:

 public static void Message(string message)
{
    Instance.Message(message);
}

And that's it! All of your code continues to use Log.Message and Log.Error, and if you'd like to change the behavior, just say

 Log.Instance = new DebugWriteLineLogger();

At runtime! You could even wrap the instance using Decorators, watch it using Observers, broadcast using Composite, etc. etc.

Solution 2: delegates

When I was saying that static types do not have means to adhere to a contract, I was not quite correct. Delegates are a more fine-granular type of a contract, which regulates methods as opposed to types. Let's define two contracts:

 public delegate void MessageLogger(string message);
public delegate void ErrorLogger(Exception exception);

Now let's specify that our Log methods define a contract, not an implementation:

 public static class Log
{
    public static MessageLogger Message { get; set; }
    public static ErrorLogger Error { get; set; }
}

We can still call Log.Message(string), but this time we can substitute an implementation at runtime:

 Log.Message = Debug.WriteLine;

Also, instead of creating explicit delegates MessageLogger and ErrorLogger, we could reuse the System ones: Action<string> and Action<Exception>, which would work just as well.

Important update: originally I wrote here that you can convert between different delegate types as long as the method signature is the same (e.g. convert MessageLogger to Action<string> and back). THIS IS WRONG. You can assign delegates of type MessageLogger and Action<string> to point to the same method, but you can't assign them to each other. Delegate type inferencing only works when assigning methods to delegates and not delegates to delegates. I'm sorry about the confusion and thanks to Alex for pointing this out.

Of course, as with the Singleton from Solution 1, we have to make sure we initialize the default implementation before we call it, otherwise we'll get a null reference exception. We always have to be careful about initializing contracts with the default implementation.

Now that'd be funny - what if we want to log a null reference exception that Log.Error is null - how in the world are we supposed to do that?

As you can see, delegates are just as powerful as interfaces when specifying a contract for a single method as opposed to whole type. I already wrote about it too: Delegates as an alternative to single-method interfaces

A peculiarity with delegates is that you can't switch all the delegates at once, you have to do it one by one. This can be both an advantage and a disadvantage, depending on your situation. Also, there is no clear guidance as to where to put the implementation methods for delegates - they might end up being scattered all over the code.

Conclusion

We can use any of the two solutions above to change the logging implementation at runtime, even if the Log class is defined in a different assembly. Moreover, we can switch between the first and the second solution without changing the client source code. We will have to recompile though, because it changes the static methods on Log to static properties and vice versa (breaks binary compatibility, not source level compatibility).

I'm still not sure whether I like the Singleton or Delegates better. It probably depends on the situation. What do you think?

Comments

  • Anonymous
    February 05, 2008
    Ridefinire i metodi statici

  • Anonymous
    February 06, 2008
    If delegates in C# support duck-typing, why lines 1 and 2 below compile just fine while 3 is not? Thank You. static void Main(string[] args) { /1/Func<object, bool> myFunc = IsNull; /2/Predicate<object> myPredicate = IsNull; /3/myPredicate = (Predicate<object>)myFunc; } static bool IsNull(object obj) {  return obj == null; }

  • Anonymous
    February 06, 2008
    Thanks Alex! I've updated the post above: Important update: originally I wrote here that you can convert between different delegate types as long as the method signature is the same (e.g. convert MessageLogger to Action<string> and back). THIS IS WRONG. You can assign delegates of type MessageLogger and Action<string> to point to the same method, but you can't assign them to each other. Delegate type inferencing only works when assigning methods to delegates and not delegates to delegates. I'm sorry about the confusion and thanks to Alex for pointing this out.

  • Anonymous
    February 20, 2008
    Cela fait longtemps que je dis que ça manque. Et bien c'est pas de main la veille que ça va changer.

  • Anonymous
    February 20, 2008
    Hello Kirill. I often use your first solution but a little different. I am not fan of Log.Instance = new DebugWriteLineLogger(); even if in this case, it is efficient. Indeed, I prefer when the singleton class manage itself her instance. So I often use this solution: http://blogs.codes-sources.com/matthieu/archive/2008/02/20/override-static.aspx (sorry it's in French but what is interesting is the C# code)

    • Anonymous
      September 24, 2017
      Hi Matthieu,I wanted to look at the solution but it looks like it may be behind a registration wall. Would it be possible to place it in CodeProject?Craig
  • Anonymous
    February 24, 2008
    Hi Matthieu, WOW! It's a great implementation! I can recognize the double-checked locking pattern. And I like your trick to create a base class for Singletons and to use generic constraints to only allow derived types. BTW I think you could use the 'new' generic constraint which denotes that the generic type parameter has a callable constructor - thus you could avoid Activator.CreateInstance, but then you'd probably end up having C1 and C2 constructors public. (Note to myself: learn French)

  • Anonymous
    January 22, 2010
    Can you give an example of the delegate solution if Log was in 2 different assemblies (two different implementations)?

  • Anonymous
    September 13, 2010
    The comment has been removed

  • Anonymous
    May 04, 2012
    Hey, nice post, thanks. I may miss your points here, but you said "There's no technical way in the language to tie two static types together with an "inheritance" contract. " However, think about this code class A{ static void a();} class B extends A { static void main(String[]){a();}} Apparently, I can call directly method a() of A in B which extends A, although I do not have right to override a() of A. Does it contradicts with your words above? Thanks you.  

  • Anonymous
    May 06, 2012
    Zell: try to redefine function a() in B ;) And I think it's beside the point... Regards, Martin

  • Anonymous
    October 28, 2013
    Solution 1 doesn't work. Instance is static, you just move the problem from a method to a property.

  • Anonymous
    August 22, 2017
    In pure OO , put simplistically, A derived class is no different to a class in which had all the inherited code in it. It is its own TypePut another way - one could replace all derived classes with Non-derived classes and the code should run identicallyIn that sense a static anything is part of the (derived) Type and nothing to do with the base Type and therefore static features SHOULD be overidable