27

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?

Impworks
  • 2,647
  • 3
  • 25
  • 53
  • I've read this same question a few days ago, but can't find it anymore. The main point is, the compiler first checks for matching _instance_ methods. The overridden methods still coutn as _declared by the base class_, so they would only be considered if there was no matching method declared in `Derived`, but `Foo(object)` does match. There was a blog by Jon Skeet (or maybe Eric Lippert) about that, still trying to find it – René Vogt Oct 05 '18 at 09:10
  • @RenéVogt: are you sure it's the compiler that checks it or the runtime? – Tim Schmelter Oct 05 '18 at 09:12
  • 1
    @TimSchmelter yes, the compiler. Found Jon Skeet's blog: http://csharpindepth.com/Articles/General/Overloading.aspx scroll down to "Inheritance". – René Vogt Oct 05 '18 at 09:13
  • @RenéVogt: i don't think it's that simple: https://stackoverflow.com/a/45872956/284240 – Tim Schmelter Oct 05 '18 at 09:14
  • @TimSchmelter but that doesn't matter here, we don't talk about virtual methods because the compiler already choose to use the non-virtual instance method `Foo(object)`. If this wouldn't exist, it would choose the virtual method and then the things you linked would happen at runtime. – René Vogt Oct 05 '18 at 09:16
  • Btw, here is a fiddle that shows OP's code: https://dotnetfiddle.net/O7HJ6L If you uncomment the overloaded method it will be called and the output will be: _"Derived.Foo(object)"_ – Tim Schmelter Oct 05 '18 at 09:20

2 Answers2

29

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.
TheGeneral
  • 79,002
  • 9
  • 103
  • 141
  • I honestly don’t understand how the spec applies to OP’s situation since *`Derived` explicitly overrides `Foo(int x)`*. So `Foo(int)` is *as close as* `Foo(object)` for the purpose of overload resolution in the `d.foo(i)` call. I’d agree if `Derived` hadn’t overridden the base class method but this isn’t the case here. Eric Lippert’s clarification does apply since it explicitly talks about “first *declared*”. But this doesn’t seem to be borne out by the specs, and is at any rate not really justified by his “brittle base class” rationale. – Konrad Rudolph Oct 05 '18 at 15:56
  • @KonradRudolph: Whether a class *overrides* a method or not is an *implementation detail* of that class. Your proposal is basically that *if I change whether or not a class overrides a method, that changes how overload resolution works*, and that is a brittle base class failure! Think about it this way: suppose I have classes A, B, C, D, where D : C, C : B, B : A. Suppose A declares a virtual method M, and C overrides it. You're telling me that *overload resolution for D.M might change if I move the override from C to B*. That's a bad user experience. – Eric Lippert Oct 05 '18 at 16:52
  • 2
    @KonradRudolph: Now, your point "I don't understand how the spec applies" is well taken, and that's because Saruman has quoted the wrong section of the spec. That's the section on the **run time processing** of the method call. The question is about **compile time overload resolution**. – Eric Lippert Oct 05 '18 at 17:40
  • @Saruman, the section of the spec you should have quoted is the one that reads **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.** Note that as KonradRudolph notes, the "declared" is the important part there. – Eric Lippert Oct 05 '18 at 17:44
  • @EricLippert I disagree that it’s fundamentally/necessarily an implementation detail — this may well be the case in C# (mouth of horse, etc.) but it’s certainly not the case in other languages (e.g. C++), which do not suffer from the brittle base class case either. – Konrad Rudolph Oct 05 '18 at 18:05
  • 1
    @KonradRudolph: Well, C# and C++ are different. First, C# was intended to address design shortcomings of C++, and this is one of them. Second, in my experience it is very common to have *all* the source code of a C++ program in front of you when you derive from a base class. In C# it is very common to derive from a base class in a library where all you have is metadata. In the C# scenario it is much more likely that you'd view overriding decisions as implementation details rather than as artifacts directly visible in the code you're reading. – Eric Lippert Oct 05 '18 at 18:46
15

The accepted answer is correct (excepting the fact that it quotes the wrong section of the spec) but it explains things from the perspective of the specification rather than giving a justification for why the specification is good.

Let's suppose we have base class B and derived class D. B has a method M that takes Giraffe. Now, remember, by assumption, the author of D knows everything about B's public and protected members. Put another way: the author of D must know more than the author of B, because D was written after B, and D was written to extend B to a scenario not already handled by B. We should therefore trust that the author of D is doing a better job of implementing all functionality of D than the author of B.

If the author of D makes an overload of M that takes an Animal, they are saying I know better than the author of B how to deal with Animals, and that includes Giraffes. We we should expect overload resolution when given a call to D.M(Giraffe) to call D.M(Animal), and not B.M(Giraffe).

Let's put this another way: We are given two possible justifications:

  • A call to D.M(Giraffe) should go to B.M(Giraffe) because Giraffe is more specific than Animal
  • A call to D.M(Giraffe) should go to D.M(Animal) because D is more specific than B

Both justifications are about specificity, so which justification is better? We're not calling any method on Animal! We're calling the method on D, so that specificity should be the one that wins. The specificity of the receiver is far, far more important than the specificity of any of its parameters. The parameter types are there for tie breaking. The important thing is making sure we choose the most specific receiver because that method was written later by someone with more knowledge of the scenario that D is intended to handle.

Now, you might say, what if the author of D has also overridden B.M(Giraffe)? There are two arguments why a call to D.M(Giraffe) should call D.M(Animal) in this case.

First, the author of D should know that D.M(Animal) can be called with a Giraffe, and it must be written do the right thing. So it should not matter from the user's perspective whether the call is resolved to D.M(Animal) or B.M(Giraffe), because D has been written correctly to do the right thing.

Second, whether the author of D has overridden a method of B or not is an implementation detail of D, and not part of the public surface area. Put another way: it would be very strange if changing whether or not a method was overridden changes which method is chosen. Imagine if you're calling a method on some base class in one version, and then in the next version the author of the base class makes a minor change to whether a method is overridden or not; you would not expect overload resolution in the derived class to change. C# has been designed carefully to prevent this kind of failure.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • 1
    Very elegant and eloquent description, and thanks for the comments and corrections... I am always honored to be corrected by you and read your posts. – TheGeneral Oct 05 '18 at 21:24