That's a simultaneously fun and annoying problem to have! :) I believe that one of the missing links here is this:
- The
override modifier is related to virtual or abstract methods; a method with an override modifier simply provides an alternative implementation to an existing virtual or abstract method. A virtual method has a default implementation, an abstract method has no implementation at all.
- The
new modifier however can be applied to any method and simply allows the reuse of a name that was already taken. These methods are not related to each other at all, the only similarity being that they forcibly share the same name.
The problem with new is that the type using a new method "pretends" that the original implementation never existed. The base class however is absolutely unaware of this - new severs the link in the hierarchy, and this is what's casing your problem.
Generally speaking, you do not want to use the new modifier.
That, and you probably wanted to use var m = new Program(); instead - reasons being explained below.
Consider these two pieces of code:
LogThings a = new DoMath();
a.WriteLog("something");
and
LogThings a = new Program();
a.WriteLog("something");
At this point, the method being called is LogThings.WriteLog(). Even though we instantiate a DoMath or Program class that provides a new method, the LogThings part of the world doesn't "know" that. It instead believes to have a virtual method that doesn't happen to be overridden. As a result, this code prints:
This is the base in LogThings something
As mentioned above: new severs that link.
In the next example, the method being called is indeed DoMath.WriteLog(), because we're now simply instantiating the DoMath() class and call its LogThings method.
DoMath b = new DoMath();
b.WriteLog("something");
Not surprisingly, this code prints
this is overriding the base in DoMath
This is the base in LogThings something
Note that it does not print "this is not overriding" because we did not instantiate an instance of the Program class. Likewise, the base.LogThings() call has nothing to do with "overriding", it simply changes the focus to the LogThings type and calls whatever implementation it knows.
This is similar to the original code you used:
var m = new DoMath();
Lastly, consider this version:
DoMath c = new Program();
c.WriteLog("something");
Here, the Program class actually overrides the virtual void WriteLog method of DoMath. Consequently, this code prints
this is not overriding.
... which is now wrong, because it does.
The key to understanding this is that each class containing virtual or abstract methods has what's called a virtual function table, or vtable. This vtable is "inherited" by derived classes and allows the compiler to know which implementation of a method to call (through a so-called virtual dispatch).
You can consider an entry in the vtable to be something like a pointer to the actual implementation of a method (the one from the current class), followed by a pointer to the previous implementation.
In your example of DoMath and Program, instantiating the DoMath class would produce a vtable consisting of only
DoMath.WriteLog(string) -> null
whereas instantiating the Program class would produce an entry like this:
Program.WriteLog(string) -> DoMath.WriteLog(string) -> null
This is why ((DoMath)new Program()).WriteLog() works - even though we look at a DoMath reference, the compiler looks up the vtable for the instantiated type (Program) and can follow the chain up to the actual implementation (Program.WriteLog).
Do however note the makeshift null in there. Because the DoMath class declares the WriteLog method as new, it is considered to be a - well - new method, which is unrelated to the one from LogThings. For LogThings, the vtable world still looks somewhat like this:
LogThings.WriteLog(string) -> null
Because there is no legit override - just a different method that happens to have the same name - ((LogThings)new Program()).WriteLog() calls LogThings.WriteLog(), as that's the last implementation in the chain. The new essentially "forces in" another vtable, resulting in this somewhat split-brained setup.
Please note that this description of a vtable is drastically oversimplified; there's plenty of good material out there on that topic however.