C# method override resolution weirdness

  • A+
Category:Languages

Consider the following snippet of code:

using System;  class Base {     public virtual void Foo(int x)     {         Console.WriteLine("Base.Foo(int)");     } }  class Derived : Base {     public override void Foo(int x)     {         Console.WriteLine("Derived.Foo(int)");     }      public void Foo(object o)     {         Console.WriteLine("Derived.Foo(object)");     } }  public class Program {     public static void Main()     {         Derived d = new Derived();         int i = 10;         d.Foo(i);     } } 

And the surprising output is:

Derived.Foo(object) 

I would expect it to select the overridden Foo(int x) method, since it's more specific. However, C# compiler picks the non-inherited Foo(object o) version. This also causes a boxing operation.

What is the reason for this behaviour?

 


This is the rule, and you may not like it,

Quote from Eric Lippert

if any method on a more-derived class is an applicable candidate, it is automatically better than any method on a less-derived class, even if the less-derived method has a better signature match.

The reason is because the method that is a better signature match might have been added in a later version and thereby be introducing a "brittle base class" failure


Note : This is a fairly complicated and in-depth part of the C# specs and jumps all over the place. However, the main part of the issue you are experiencing are written as follows

C# Language Specification Version 5.0

7.5.5 Function member invocation

...

The run-time processing of a function member invocation consists of the following steps, where M is the function member and, if M is an instance member, E is the instance expression:

...

If M is an instance function member declared in a reference-type:

  • E is evaluated. If this evaluation causes an exception, then no further steps are executed.
  • The argument list is evaluated as described in §7.5.1.
  • If the type of E is a value-type, a boxing conversion (§4.3.1) is performed to convert E to type object, and E is considered to be of type object in the following steps. In this case, M could only be a member of System.Object.
  • The value of E is checked to be valid. If the value of E is null, a System.NullReferenceException is thrown and no further steps are executed.
  • The function member implementation to invoke is determined:
    • If the binding-time type of E is an interface, the function member to invoke is the implementation of M provided by the run-time type of the instance referenced by E. This function member is determined by applying the interface mapping rules (§13.4.4) to determine the implementation of M provided by the run-time type of the instance referenced by E.
    • Otherwise, if M is a virtual function member, the function member to invoke is the implementation of M provided by the run-time type of the instance referenced by E. This function member is determined by applying the rules for determining the most derived implementation (§10.6.3) of M with respect to the run-time type of the instance referenced by E.
    • Otherwise, M is a non-virtual function member, and the function member to invoke is M itself.

After reading the specs what's interesting is, if you use an interface which describes the method, the compiler will choose the overload signature, in-turn work as expected

  public interface ITest   {      void Foo(int x);   } 

Which can be shown here

In regards to the interface, it does make sense when considering the overloading behavior was implemented to protect against Brittle base class


Additional Resources

Eric Lippert, Closer is better

The aspect of overload resolution in C# I want to talk about today is really the fundamental rule by which one potential overload is judged to be better than another for a given call site: closer is always better than farther away. There are a number of ways to characterize “closeness” in C#. Let’s start with the closest and move our way out:

  • A method first declared in a derived class is closer than a method first declared in a base class.
  • A method in a nested class is closer than a method in a containing class.
  • Any method of the receiving type is closer than any extension method.
  • An extension method found in a class in a nested namespace is closer than an extension method found in a class in an outer namespace.
  • An extension method found in a class in the current namespace is closer than an extension method found in a class in a namespace mentioned by a using directive.
  • An extension method found in a class in a namespace mentioned in a using directive where the directive is in a nested namespace is closer than an extension method found in a class in a namespace mentioned in a using directive where the directive is in an outer namespace.

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: