Without hesitation, let's just start off with a code sample. When running this program, what output would you expect?

using System;
namespace Alcedo.Demo
{
    class Base { }
    class Derived : Base { }

    class Worker
    {
        public virtual void Work(Derived param)
        {
            Console.WriteLine("Worker.Work(Derived)");
        }
    }

    class SpecializedWorker : Worker
    {
        public override void Work(Derived param)
        {
            Console.WriteLine("SpecializedWorker.Work(Derived)");
        }

        public void Work(Base param)
        {
            Console.WriteLine("SpecializedWorker.Work(Base)");
        }
    }

    class Program
    {
        public static void Main()
        {
            Derived param = new Derived();
            SpecializedWorker worker = new SpecializedWorker();
            worker.Work(param);
        }
    }   
}

Think about it for a while before reading on.

.

.

.

.

.

.

.

That should do. The output is SpecializedWorker.Work(Base). If that was what you expected (and you know why) you can stop reading here. If not, keep reading for the explanation.

The decision of what method to call can happen in one of two places; either it is done by the compiler, or it is done by the run-time. Well, the compiler is always involved, but in the case of virtual methods, it is ultimately the run-time that decides, based on the actual run-time types of the objects involved. That reasoning seems to indicate that the output of the program really ought to be SpecializedWorker.Work(Derived). After all, we invoke a method called Work on an object of the type SpecializedWorker, passing an object of the type Derived to it. Surely the run-time should have called SpecializedWorker.Work(Derived)?

It turns out the run-time is not even involved in the decision.

Given the code above, the compiler will make an attempt to decide what method to call. This follows a rather detailed specification (that you can read here: C# Language Specification, Method invocations).

In brief, it starts off by identifying all possible candidate methods. A method is a candidate if it:

  • Has the same name (in our case Work)
  • It is declared in the type (in our case SpecializedWorker) or one of its base types
  • It is not declared with the override modifier
At this point there are two candidates to choose from:
public virtual void Worker.Work(Derived)
public void SpecializedWorker.Work(Base)

OK, so at this point SpecializedWorker.Work(Derived param) is not in the candidate list any longer. In fact calls to override methods should never be; they are called as a result of overload resolution at run-time, not based on a decision of the compiler. It would still be called if the compiler decided on using the virtual Worker.Work(Derived) method. But the compiler does not choose the virtual method. Why?

The answer is found in the Method Invocations document linked above:

If N is applicable with respect to A (Section 7.4.2.1), then all methods declared in a base type of T are removed from the set.

According to the section linked above, a method is considered to be applicable if the number of arguments to the method is identical to the argument list in the call, and if there is an implicit conversion from each argument's type, to the corresponding argument of the method.

Both candidates meet this requirement; both have exactly one argument, and the type Derived can implicitly be converted to the argument type of each of the two methods. The result of the quoted text from the specification is that in this case the virtual Worker.Work(Derived) method will be removed from the list of candidates since it is declared in a base type of SpecializedWorker.

And now the list of candidates is reduced to one single method: SpecializedWorker.Work(Base).

kick it on DotNetKicks.com


I am working for a great consultancy company called Diversify, located in Sweden. We are hiring skilled .NET developers. If you are interested, don't hesitate to get in touch with me.

Comments

September 9. 2010 17:37

trackback

Fun with method overloading

You've been kicked (a good thing) - Trackback from DotNetKicks.com

DotNetKicks.com