![](/img/trans.png)
[英]Force C# compiler to use non-generic method overload with Linq Expression parameter
[英]Why C# compiler use an invalid method's overload?
以下代码使我感到困惑
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);
代码执行的结果是“ B”写入控制台。
实际上,B类包含Abc方法的两个重载,第一个重载为int参数,第二个重载为double。 为什么编译器对整数参数使用双精度版本?
请注意,方法abc(double)不会遮蔽或覆盖方法abc(int)
由于编译器可以将int隐式转换为double,因此选择B.Abc方法。 乔恩·斯基特 ( Jon Skeet)在这篇文章中对此进行了解释(搜索“隐式”):
方法调用的目标是类型Child的表达式,因此编译器首先查看Child类。 那里只有一种方法,并且它是适用的(从int到double的隐式转换),因此才被选择。 编译器根本不考虑Parent方法。
这样做的原因是为了减少发生脆性基类问题的风险。
如标准所言,“如果派生类中的任何方法均适用,则基类中的方法不是候选对象”。
换句话说,重载解决方案算法从类中搜索适用的方法开始。 如果找到一个,则将更深的基类中的所有其他适用方法从候选集中删除,以进行重载解析。 由于Delta.Frob(float)适用,因此Charlie.Frob(int)甚至都不会被视为候选对象。 只有在最派生的类型中找不到适用的候选对象时,我们才开始查看其基类。
如果我们使用来自A的这个附加类扩展您问题中的示例,事情会变得更加有趣。
class C : A {
public void Abc(byte b) {
Console.Write("C");
}
}
如果我们执行以下代码
int i = 1;
b.Abc((int)1);
b.Abc(i);
c.Abc((int)1);
c.Abc(i);
结果是BBCA
。 这是因为对于B类,编译器知道它可以隐式将任何 int转换为double。 对于C类,编译器知道它可以将文字int 1强制转换为字节(因为值1适合一个字节),因此可以使用C的Abc方法。 但是,编译器无法将任何旧的int隐式转换为字节,因此c.Abc(i)
不能使用C的Abc方法。 在这种情况下,它必须使用父类。
此页面在隐式数值转换上显示了一个紧凑的表,其中数字类型具有到其他数值类型的隐式转换。
即使将B
定义为以下内容,也可以获得相同的功能:
class B : A
{
public void Abc(object p)
{
Console.Write("B");
}
}
简而言之,这是因为重载解析是通过查看当前类中定义的方法来完成的。 如果当前类中有任何合适的方法,它将停止查找。 仅当没有合适的匹配项时,它才会查看基类
您可以查看过载分辨率规范以获得详细说明。
不同的语言(例如C ++,Java或C#)具有非常不同的重载解析规则。 在C#中,根据语言规范正确选择了重载。 如果要选择其他重载,则可以选择。 记住这一点:
当派生类打算为继承的方法声明另一个重载,以便将所有可用的重载都视为具有同等权利的对等体时,它还必须通过基调用显式重写所有继承的重载。
要求进行此练习的语言设计优势是什么?
想象一下,您正在使用第三方库(例如.NET框架),并且是从其一个类派生的。 在某个时候,您引入了一个称为Abc
的私有方法(一个新的唯一名称,没有任何重载)。 两年后,您升级了第三方库版本,而没有注意到他们也添加了一种方法,您可以访问该方法,并且很遗憾地调用了Abc
,只是它在某处具有不同的参数类型(因此,升级不会通过编译来提醒您)时间误差),并且其行为略有不同,甚至目的也完全不同。 您是否真的希望将对Abc
的私人通话的一半无声地重定向到第三方Abc
? 在Java中,可能会发生这种情况。 在C#或C ++中,这不会发生。
C#方式的好处是,对于重新分发的库,添加功能的同时要严格保持向后兼容性,这会比较容易。 实际上有两种方式:
C#方式的缺点是,它会改变OOP的哲学,即仅更改实现而不更改类的API的重写方法。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.