具有相同名称的静态方法和扩展方法

我创建了扩展方法:

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 Member 'decimal.Floor(decimal)' cannot be accessed with an instance reference; qualify it with a type name instead 。 我明白有静态decimal.Floor(decimal)方法。 但它有不同的签名。 为什么编译器无法选择正确的方法?

你在这里有两个好的和正确的答案,但我理解简单引用规范的答案并不总是那么有启发性。 让我补充一些其他细节。

您可能有一个重载分辨率的心智模型,如下所示:

  • 将所有可能的方法放在一个大桶中 – 扩展方法,静态方法,实例方法等。
  • 如果存在可能使用的错误的方法,请从存储桶中删除它们。
  • 在其余方法中,选择与参数类型具有最佳匹配的唯一方法。

虽然这是很多人超载分辨率的心理模型,但令人遗憾的是它是巧妙的错误。

真正的模型 – 我将在这里忽略generics类型推断问题 – 如下:

  • 将所有实例和静态方法放在存储桶中。 虚拟覆盖不计为实例方法。
  • 消除不适用的方法,因为参数与参数不匹配。

在这一点上,我们要么有方法,要么我们没有。 如果我们在桶中有任何方法, 则不检查扩展方法 。 这是重要的一点。 该模型不是 “如果正常的重载分辨率产生错误,那么我们检查扩展方法”。 该模型是“如果正常的重载分辨率没有产生任何适用的方法,那么我们检查扩展方法”。

如果存储桶中有方法,则会更多地消除基类方法,最后根据参数与参数的匹配程度选择最佳方法。

如果碰巧选择静态方法,那么C#将假设您打算使用类型名称并错误地使用实例 ,而不是您希望搜索扩展方法 。 重载分辨率已经确定存在一个实例或静态方法,其参数与您给出的参数匹配,并且它将选择其中一个或给出错误; 它不会说“哦,你可能打算把这种古怪的扩展方法称为恰好在范围内”。

我知道从你的角度来看这很令人烦恼。 您显然希望模型“如果重载决策产生错误,则回退到扩展方法”。 在您的示例中,这将是有用的,但此行为会在其他方案中产生不良结果。 例如,假设你有类似的东西

 mystring.Join(foo, bar); 

这里给出的错误是它应该是string.Join 。 如果C#编译器说“哦, string.Join是静态的,那将是奇怪的。用户可能打算使用连接字符序列的扩展方法,让我试试……”然后你收到一条错误信息说序列连接运算符 – 这里没有任何关于你的代码的东西 – 没有正确的参数。

或者更糟糕的是,如果通过一些奇迹你确实给它的参数有效但是打算调用静态方法,那么你的代码将以一种非常奇怪且难以调试的方式被破坏。

扩展方法在游戏的最后阶段添加,并且查找它们的规则使得他们故意更喜欢给出神奇的工作错误。 这是一个安全系统,以确保扩展方法不受意外的约束。

决定调用哪个方法的过程包含C#语言规范中描述的许多小细节。 适用于您的场景的关键点是,只有当编译器无法在接收类型本身的方法(即decimal )中找到要调用的方法时,才会考虑扩展方法进行调用。

以下是规范的相关部分:

构造了方法调用的候选方法集。 对于与方法组M关联的每个方法F:

  • 如果F是非generics的,则F是以下情况的候选者:

  • M没有类型参数列表,并且

  • F适用于A(§7.5.3.1)。

根据以上所述, double.Floor(decimal)是一个有效的候选人。

如果生成的候选方法集合为空,则放弃沿着以下步骤的进一步处理,而是尝试将调用作为扩展方法调用进行处理(第7.6.5.2节)。 如果失败,则不存在适用的方法,并发生绑定时错误。

在您的情况下,候选方法集不为空,因此不考虑扩展方法。

decimal.Floor的签名是

 public static Decimal Floor(Decimal d); 

我不是类型推断的专家,但我想因为从intDecimal的隐式转换,编译器选择它作为最佳拟合方法。

如果您将签名更改为

 public static decimal Floor( this decimal value, double precision) 

并称之为

 (10.1234m).Floor(2d) 

有用。 但当然,精度的double有点奇怪。

编辑: Eric Lippert关于算法的引用:

接收类型的任何方法都比任何扩展方法更接近。

Floor是“接收类型”( Decimal )的方法。 关于C#开发人员为什么这样实现它,我不能做任何陈述。