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

时间:2015-12-16 16:59:45

标签: c# extension-methods static-methods

我创建了扩展方法:

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)方法。但它有不同的签名。为什么编译器无法选择正确的方法?

3 个答案:

答案 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);

我不是类型推断方面的专家,但我想由于存在从intDecimal的隐式转换,编译器会选择此作为最佳拟合方法。

如果您将签名更改为

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

并将其称为

(10.1234m).Floor(2d)

它有效。但当然double精度有点奇怪。

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

  

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

Floor是"接收类型的方法" (Decimal)。在为什么 C#开发人员这样实现它我不能做任何声明。