2

In the given example (sorry for code style, it was in the interest of a compact self-contained repro), the method called is always the virtual method in B that has the additional integer parameter, while I would expect that the method implemented in C would be the one called.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MethodChoosing
{
    class Program
    {
        static void Main(string[] args)
        {
            C c = new C();
            c.M();

            Console.ReadKey();
        }
    }

    public class A
    {
        public virtual void M(bool? a = null)
        {
            Console.WriteLine("base base");
        }
    }

    public class B : A
    {
        public override void M(bool? a = null)
        {
            Console.WriteLine("base override");
        }

        public virtual void M(bool? a = null, int? b = null)
        {
            Console.Write("base new");
        }
    }

    public class C : B
    {
        public override void M(bool? a = null)
        {
            Console.WriteLine("pick me!");
        }
    }
}

Which outputs "base new". This is obviously not the behaviour I expected, is anyone able to explain the reasoning?

Edit: Changing main so that c.M has a true or null parameter still selects the incorrect method.

Mathew Thompson
  • 55,877
  • 15
  • 127
  • 148
one-t
  • 333
  • 3
  • 13
  • check out this question: http://stackoverflow.com/questions/8909811/c-sharp-optional-parameters-on-overridden-methods it might not be the same thing but it is close. – John Sobolewski Mar 27 '12 at 13:45
  • You've combined overloading and optional arguments. You should never do that. For your crime I sentence you to read [Eric Lippert's four part series on optional argument corner cases](http://blogs.msdn.com/b/ericlippert/archive/2011/05/09/optional-argument-corner-cases-part-one.aspx) plus this ["if it hurts, then don't do it" prequel](http://blogs.msdn.com/b/ericlippert/archive/2011/02/10/optional-arguments-on-both-ends.aspx). – Craig Stuntz Mar 27 '12 at 13:46
  • I'd never do this normally, it was a random thought that turned into some investigation :) – one-t Mar 27 '12 at 16:00

3 Answers3

5

Section 7.4 of the spec states that override methods are excluded from the set of accessible methods being considered during member lookup. Considering the methods in the original question, class A has a single non overridden method, class B has a single non overridden method, and class C doesnt have any. From the two methods in the set, the compiler selects the matching method in the most derived class (in this case class B, the "base new" function).

If the matching function in class B were not present, the best function match would be the virtual function in class A. In that case, being virtual, the run time type of the object would be used to select which of the overridden functions in either class A, B or C would be chosen. Which as the question was stated, as the method was called on an object of class C, would be the "pick me!" function.

Targon
  • 66
  • 1
  • 4
1

With c.M() it is assumed that M(null, null) is called. That is the way how optional parameters are working. If you want C.M to be called, call it with C.M(null) or override M(bool? a = null, int? b = null) in C class.

Mathew Thompson
  • 55,877
  • 15
  • 127
  • 148
Dejo
  • 2,078
  • 3
  • 26
  • 38
1

That's whats called an Ambiguous Invocation. As you have two methods separated only by one optional parameter, the application seems to choose the method that it can satisfy the most parameters for.

Edit: as @lazyberezovsky stated, I wasn't entirely correct there. IF you have a method it can satisfy with NO parameters, that takes precedence

Mathew Thompson
  • 55,877
  • 15
  • 127
  • 148
  • Ah yes, I've just matched the number of parameters in C and it then prints "pick me!". – one-t Mar 27 '12 at 14:13
  • Having passed a parameter to C.M, both true and null, it still chooses the same method. Which seems strange because I'm explicitly providing enough parameters to fulfill the method that takes one parameter, but it it's still selecting the one with the most optional parameters. – one-t Mar 27 '12 at 14:52
  • Yes because you're still matching the method with the highest number of parameters. The joys of optional parameters :D. – Mathew Thompson Mar 27 '12 at 14:59
  • Remove all parameters from virtual void M() in class B. And run program. Then downvote yourself :) – Sergey Berezovskiy Mar 27 '12 at 15:06
  • Well not entirely:P. The only difference in what I've said is that if you have a method that the call satisfies with NO parameters, that method will take precedence. The other method becomes hidden by the overload with no parameters. – Mathew Thompson Mar 27 '12 at 15:11
  • Not exactly, if you add one optional parameter to B.M(int x=0) and two optional parameters to all other methods, anyway, B.M(int x=0) will be executed. The only way it works as expected, when you do polymorphic call via instance of base type. – Sergey Berezovskiy Mar 27 '12 at 15:18
  • Another comment - make all methods with one default parameter e.g. int. And make that method of B class with one default parameter e.g. long. And run again c.M(). – Sergey Berezovskiy Mar 27 '12 at 15:24