Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# method override resolution weirdness

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?

like image 559
Impworks Avatar asked Oct 05 '18 08:10

Impworks


People also ask

What C is used for?

C programming language is a machine-independent programming language that is mainly used to create many types of applications and operating systems such as Windows, and other complicated programs such as the Oracle database, Git, Python interpreter, and games and is considered a programming foundation in the process of ...

What is C full form?

Full form of C is “COMPILE”. One thing which was missing in C language was further added to C++ that is 'the concept of CLASSES'.

What is C language basics?

What is C? C is a general-purpose programming language created by Dennis Ritchie at the Bell Laboratories in 1972. It is a very popular language, despite being old. C is strongly associated with UNIX, as it was developed to write the UNIX operating system.

Is C language easy?

C is a general-purpose language that most programmers learn before moving on to more complex languages. From Unix and Windows to Tic Tac Toe and Photoshop, several of the most commonly used applications today have been built on C. It is easy to learn because: A simple syntax with only 32 keywords.


1 Answers

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/in-depth part of the C# specs and it jumps all over the place. However, the main parts of the issue you are experiencing are written as follows

Update

And this is why i like stackoverflow, It is such a great place to learn.

I was quoting the the section on the run time processing of the method call. Where as the question is about compile time overload resolution, and should be.

7.6.5.1 Method invocations

...

The set of candidate methods is reduced to contain only methods from the most derived types: For each method C.F in the set, where C is the type in which the method F is declared, all methods declared in a base type of C are removed from the set. Furthermore, if C is a class type other than object, all methods declared in an interface type are removed from the set. (This latter rule only has affect when the method group was the result of a member lookup on a type parameter having an effective base class other than object and a non-empty effective interface set.)

Please see Eric's post answer https://stackoverflow.com/a/52670391/1612975 for a full detail on whats going on here and the appropriate part of the specs

Original

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 working 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.
like image 116
TheGeneral Avatar answered Sep 22 '22 21:09

TheGeneral