To simplify the matter as much as possible here is the case:
var input = ... var x = new X(); var output = x.Execute(input);
You pass correct input, and get correct output. Simple, right? But it did not work! So, we delved into the foreign code, and this is what we have seen:
class X: Y { public Output Execute(Input input) { return Perform(input); } protected override Output Run(Input input) { ... return output; } } class Y: Z { ... } class Z { protected Output Perform(Input input) { return Run(Input); } protected virtual Output Run(Input input) { return null; } }
Do you see, still flow is simple, right? We call X.Execute(), it calls Z.Perform(), which in turn calls overriden X.Run() that returns the result.
X.Execute()
Z.Perform()
X.Run()
But to our puzzlement we got null on output, as if Z.Run() was called!
null
Z.Run()
We stepped through the code in debugger and confirmed that Z.Perform() calls Z.Run(), even though "this" instance is of type X.
this
X
How can it be? It's a nonsence! Yet, no overriden method was ever called.
No matter how much scrunity we applied to sources X and Z it just did not work.
Z
We verified that the signature of X.Run() matches the signature of Z.Run(), so it overrides the method.
Then what do we see here?
And then enlightenment come! Yes, X.Run() overrides the method, but what method?
We looked closely at class Y, and bingo, we can see there following:
Y
class Y: Z { ... protected virtual Output Run(Input input) { return null; } ... }
So, X.Run() overrides Y.Run() and not Z.Run()!
Y.Run()
Per .NET Y.Run() and Z.Run() are two independant virtual methods, where Y.Run() in addition hides Z.Run().
IDE even issued a warning that it's better declare Y.Run() as:
protected new virtual Output Run(Input input) { return null; }
So, someones code was plainly wrong: Y.Run() had to use override rather than virtual.
override
virtual
We won, right?
Well, it's hard to call it a win.
We spent a hour looking at someones ugly code just to prove we're still sane.
So, what is conclusion of this story?
We think here it is: