简体   繁体   English

为什么C#编译器使用无效方法的重载?

[英]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. 代码执行的结果是“ B”写入控制台。

In fact the B class contains two overloads of Abc method, the first for int parameter, the second one for double. 实际上,B类包含Abc方法的两个重载,第一个重载为int参数,第二个重载为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) 请注意,方法abc(double)不会遮蔽或覆盖方法abc(int)

Since the compiler can implicitly convert the int to double, it chooses the B.Abc method. 由于编译器可以将int隐式转换为double,因此选择B.Abc方法。 This is explained in this post by Jon Skeet (search for "implicit"): 乔恩·斯基特Jon Skeet)这篇文章对此进行了解释(搜索“隐式”):

The target of the method call is an expression of type Child, so the compiler first looks at the Child class. 方法调用的目标是类型Child的表达式,因此编译器首先查看Child类。 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. 那里只有一种方法,并且它是适用的(从int到double的隐式转换),因此才被选择。 The compiler doesn't consider the Parent method at all. 编译器根本不考虑Parent方法。

The reason for this is to reduce the risk of the brittle base class problem... 这样做的原因是为了减少发生脆性基类问题的风险。

More from Eric Lippert 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. 由于Delta.Frob(float)适用,因此Charlie.Frob(int)甚至都不会被视为候选对象。 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: 如果我们使用来自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 . 结果是BBCA This is because in the case of the B class, the compiler knows it can implicitly cast any int to double. 这是因为对于B类,编译器知道它可以隐式将任何 int转换为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. 对于C类,编译器知道它可以将文字int 1强制转换为字节(因为值1适合一个字节),因此可以使用C的Abc方法。 The compiler, however, can't implicitly cast any old int to a byte, so c.Abc(i) can't use C's Abc method. 但是,编译器无法将任何旧的int隐式转换为字节,因此c.Abc(i)不能使用C的Abc方法。 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: 即使将B定义为以下内容,也可以获得相同的功能:

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. 不同的语言(例如C ++,Java或C#)具有非常不同的重载解析规则。 In C#, the overload was correctly chosen as per the language spec. 在C#中,根据语言规范正确选择了重载。 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. 想象一下,您正在使用第三方库(例如.NET框架),并且是从其一个类派生的。 At some point you introduce a private method called Abc (a new, unique name, not an overload of anything). 在某个时候,您引入了一个称为Abc的私有方法(一个新的唯一名称,没有任何重载)。 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. 两年后,您升级了第三方库版本,而没有注意到他们也添加了一种方法,您可以访问该方法,并且很遗憾地调用了Abc ,只是它在某处具有不同的参数类型(因此,升级不会通过编译来提醒您)时间误差),并且其行为略有不同,甚至目的也完全不同。 Do you really want one half of your private calls to Abc to be silently redirected to the 3rd party Abc ? 您是否真的希望将对Abc的私人通话的一半无声地重定向到第三方Abc In Java, this may happen. 在Java中,可能会发生这种情况。 In C# or C++, this isn't going to happen. 在C#或C ++中,这不会发生。

The upside of the C# way is that it's somewhat easier, for a redistributed library, to add functionality while rigorously keeping backward compatibility. C#方式的好处是,对于重新分发的库,添加功能的同时要严格保持向后兼容性,这会比较容易。 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. C#方式的缺点是,它会改变OOP的哲学,即仅更改实现而不更改类的API的重写方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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