简体   繁体   中英

Why C# compiler use an invalid method's overload?

I have been confused by the following code

class A
{
    public void Abc(int q)
    {
        Console.Write("A");
    }
}

class B : A
{
    public void Abc(double p)
    {
        Console.Write("B");
    }
}

    ...
    var b = new B();
    b.Abc((int)1);

The result of code execution is "B" written to console.

In fact the B class contains two overloads of Abc method, the first for int parameter, the second one for double. Why the compiler use a double version for an integer argument?

Be careful the method abc(double) doesn't shadow or override the method abc(int)

Since the compiler can implicitly convert the int to double, it chooses the B.Abc method. This is explained in this post by Jon Skeet (search for "implicit"):

The target of the method call is an expression of type Child, so the compiler first looks at the Child class. There's only one method there, and it's applicable (there's an implicit conversion from int to double) so that's the one that gets picked. The compiler doesn't consider the Parent method at all.

The reason for this is to reduce the risk of the brittle base class problem...

More from Eric Lippert

As the standard says, “methods in a base class are not candidates if any method in a derived class is applicable”.

In other words, the overload resolution algorithm starts by searching the class for an applicable method. If it finds one then all the other applicable methods in deeper base classes are removed from the candidate set for overload resolution. Since Delta.Frob(float) is applicable, Charlie.Frob(int) is never even considered as a candidate. Only if no applicable candidates are found in the most derived type do we start looking at its base class.

Things get a little more interesting if we extend the example in your question with this additional class that descends from A:

class C : A {
    public void Abc(byte b) {
        Console.Write("C");
    }
}

If we execute the following code

int i = 1;
b.Abc((int)1);
b.Abc(i);
c.Abc((int)1);
c.Abc(i);

the results are BBCA . This is because in the case of the B class, the compiler knows it can implicitly cast any int to double. In the case of the C class, the compiler knows it can cast the literal int 1 to a byte (because the value 1 fits in a byte) so C's Abc method gets used. The compiler, however, can't implicitly cast any old int to a byte, so c.Abc(i) can't use C's Abc method. It must use the parent class in that case.

This page on Implicit Numeric Conversions shows a compact table of which numeric types have implicit conversions to other numeric types.

You get the same functionality even when you define B as:

class B : A
{
    public void Abc(object p)
    {
        Console.Write("B");
    }
}

Simply, it's because overload resolution is done by looking at methods defined in the current class . If there are any suitable methods in the current class, it stops looking. Only if there are no suitable matches does it look at base classes

You can take a look at the Overload resolution spec for a detailed explanation.

Different languages (such as C++, Java, or C#) have vastly different overload resolution rules. In C#, the overload was correctly chosen as per the language spec. If you wanted the other overload to be chosen, you have a choice. Remember this:

When a derived class intends to declare another overload for an inherited method, so as to treat all available overloads as equal-rights peers, it must also explicitly override all the inherited overloads with a base call as well.

What is the language design benefit of requiring this exercise?

Imagine that you are using a 3rd party library (say, .NET framework) and deriving from one of its classes. At some point you introduce a private method called Abc (a new, unique name, not an overload of anything). Two years later you upgrade the 3rd party library version without noticing that they also added a method, accessible to you and called, regrettably, Abc , except that it has a different parameter type somewhere (so the upgrade doesn't alert you with a compile time error) and it behaves subtly differently or maybe even has a different purpose altogether. Do you really want one half of your private calls to Abc to be silently redirected to the 3rd party Abc ? In Java, this may happen. In C# or C++, this isn't going to happen.

The upside of the C# way is that it's somewhat easier, for a redistributed library, to add functionality while rigorously keeping backward compatibility. In two ways actually:

  • You won't ever mess with your customers' private method calls inside their own code.
  • You won't ever break your customers by adding a new uniquely named method, although you must still think twice before adding an overload of YOUR own existing method.

The downside of the C# way is that it cuts a hole into the OOP philosophy of overriding methods ever changing only the implementation, but not the API of a class.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM