Search code examples
c#inheritanceoverridingoverloadingvirtual

Calling specific base method with multiple overloads and default parameters


I'm building on top of the source code for a game and are trying to call a base virtual function with overloaded methods and default parameters. I cannot change the classes I'm deriving from and need to call the function inside my own class definition of the virtual method. I'll try to explain in more detail with code.

First we have a base class A that defines a virtual function called Foo that takes one argument.

class A
{
   public virtual string Foo(int a)
   {
      return "Class A Function 1 par";
   }
}

Then a class B that overrides Foo and defines a new overloaded virtual function for Foo with two new default parameters.

class B : A
{
   public virtual string Foo(int a, int b = 0, int c = 0)
   {
      return "Class B Function 3 par";
   }

   public override string Foo(int a)
   {
      return "Class B Function 1 par";
   }
}

Then the class I need to derive from, C. It simply overrides the one parameter Foo.

class C : B
{
   public override string Foo(int a)
   {
      return "Class C Function 1 par";
   }
}

Lastly my class D which also overrides the one parameter Foo but needs to be able to call the base method of Foo also.

class D : C
{
   public override string Foo(int a)
   {
      return base.Foo(0);
   }
}

This results in calling the three parameter Foo defined in B (returning as "Class B Function 3 par") but I want to call the Foo defined in C (would return "Class C Function 1 par"). I would think this sort of overloading of virtual functions with default parameters would cause an ambiguous compiler error but it compiles fine.

Is there a way to work around this and if not why is it allowing the structure of the classes to lock me out of accessing a base method?


Solution

  • This is an unfortunate interaction between optional parameters and the rest of the language, and a good reason why you should basically never overload methods that use optional parameters (or add overloads to methods that don't). The author of B has done something that's really bad!

    The call isn't ambiguous, because the rules for method lookup don't change for base, only the rules for which method ends up invoked after overload resolution -- in effect, the overloads are determined as if the call had read ((C) this).Foo(0). For that call, B.Foo(int, int, int) is considered the only candidate because it is the nearest non-override method when walking up the inheritance chain -- it is picked before we even get to considering A.Foo(int). Had A introduced the method, there would have been no problem, as in that case the single-parameter overload would have been considered a better method.

    Had optional parameters been part of C# from the start, rather than a relatively late addition to the party (with a bit of a quirky implementation where the values are expanded at the call site) this might have been considered and somehow mitigated. As it stands the most obvious way to fix this would be by actually changing the rules for base lookup so it prefers matching the methods that exactly match the signature of the method it occurs in, but that would just complicate the already complicated rules for overload resolution even more, and it can certainly break existing code, so the chances of something like that happening are slim.

    If you can't change A, B and C there is still a way to write D to get the desired behavior, by exploiting (if that's the word) another fairly obscure C# feature that's even older: method groups!

    class D : C
    {
       public override string Foo(int a) 
       {
          Func<int, string> foo = base.Foo;
          return foo(a);
       }
    }    
    

    This unambiguously invokes C.Foo(int), because delegate conversions on method groups do not consider optional parameters, and thus B.Foo(int, int, int) is not a valid candidate, forcing us to go further up the chain and find A.Foo(int).