我创建了扩展方法:
public static class XDecimal
{
public static decimal Floor(
this decimal value,
int precision)
{
decimal step = (decimal)Math.Pow(10, precision);
return decimal.Floor(step * value) / step;
}
}
现在我尝试使用它:
(10.1234m).Floor(2)
但是编译器说Member 'decimal.Floor(decimal)' cannot be accessed with an instance reference; qualify it with a type name instead
。我知道有静态decimal.Floor(decimal)
方法。但它有不同的签名。为什么编译器无法选择正确的方法?
答案 0 :(得分:8)
这里有两个好的和正确的答案,但我理解简单引用规范的答案并不总是那么有启发性。让我补充一些其他细节。
你可能有一个重载分辨率的心智模型,如下所示:
虽然这是很多人的超载分辨率的心理模型,但遗憾的是这是巧妙的错误。
真实模型 - 我将在此忽略泛型类型推断问题 - 如下:
此时我们要么有方法,要么我们没有。如果我们在桶中有任何方法,则不检查扩展方法。这是重要的一点。该模型不"如果正常的重载决策产生错误,那么我们检查扩展方法"。该模型是"如果正常的重载决策没有产生任何适用的方法,那么我们检查扩展方法"。
如果存储桶中有方法,则会更多地消除基类方法,最后根据参数与参数的匹配程度选择最佳方法。
如果选择静态方法,那么C#将假设您打算使用类型名称并错误地使用实例,而不是您希望搜索扩展方法。重载分辨率已经确定存在一个实例或静态方法,其参数与您给出的参数匹配,并且它将选择其中一个或给出错误;它不会说"哦,你可能打算把这个古怪的扩展方法称为恰好在范围内#34;。
据我所知,从您的角度来看,这是令人烦恼的。您显然希望模型为#34;如果重载决策产生错误,则回退到扩展方法"。在您的示例中,这将是有用的,但此行为会在其他方案中产生不良结果。例如,假设您有类似
的内容mystring.Join(foo, bar);
这里给出的错误是它应该是string.Join
。如果C#编译器说"哦,string.Join
是静态的,那将是奇怪的。用户可能打算使用连接字符序列的扩展方法,让我尝试一下......"然后你得到一条错误消息,说序列连接操作符 - 这里没有任何与你的代码有关的东西 - 没有正确的参数。
或者更糟糕的是,如果通过一些奇迹你做了给它的参数有效但是打算调用静态方法,那么你的代码将以一种非常奇怪且难以调试的方式被打破
在游戏的最后阶段添加了扩展方法,并且查找它们的规则使得他们故意更喜欢给出神奇的工作错误。这是一个安全系统,以确保扩展方法不受意外限制。
答案 1 :(得分:6)
决定调用哪个方法的过程包含许多C#语言规范中描述的小细节。适用于您的场景的关键点是,仅当编译器无法在接收类型本身的方法(即decimal
)中找到要调用的方法时,才考虑使用扩展方法。
以下是规范的相关部分:
构造方法调用的候选方法集。对于与方法组M关联的每个方法F:
如果F是非泛型的,则在以下情况下F是候选者:
M没有类型参数列表,
F适用于A(§7.5.3.1)。
根据以上所述,double.Floor(decimal)
是有效的候选人。
如果生成的候选方法集合为空,则放弃沿着以下步骤的进一步处理,而是尝试将调用作为扩展方法调用进行处理(第7.6.5.2节)。如果失败,则不存在适用的方法,并发生绑定时错误。
在您的情况下,候选方法集不为空,因此不考虑扩展方法。
答案 2 :(得分:3)
decimal.Floor
的签名是
public static Decimal Floor(Decimal d);
我不是类型推断方面的专家,但我想由于存在从int
到Decimal
的隐式转换,编译器会选择此作为最佳拟合方法。
如果您将签名更改为
public static decimal Floor(
this decimal value,
double precision)
并将其称为
(10.1234m).Floor(2d)
它有效。但当然double
精度有点奇怪。
编辑:Eric Lippert关于算法的引用:
接收类型的任何方法都比任何扩展方法更接近。
Floor
是"接收类型的方法" (Decimal
)。在为什么 C#开发人员这样实现它我不能做任何声明。