问题 扩展方法与静态方法优先级


考虑以下程序:

class A
{
    public static void Foo()
    {
    }
}

static class Ext
{
    public static void Foo(this A a)
    {
    }
}

class Program
{
    static void Main(string[] args)
    {
        var a = new A();
        a.Foo();
    }
}

这无法编译,错误如下:

无法使用实例引用访问成员'Test.A.Foo()';用类型名称来限定它

为什么编译器会忽略扩展方法?


12032
2018-03-28 18:27


起源

你为什么想做这个?它有可能导致大规模的混乱! - Jetti
嗯挠我的头 - GETah
如果这是一个有效的代码,你会引入一种难以让人理解的模糊性,更不用说机器来解释你的意图了。我不认为这是一个实际问题。如果要调用应用此逻辑的外部静态方法而不是依赖重叠的原型系统,您可能希望直接调用该方法: Ext.Foo(a); 因此,所有参与者的意图都是明确的。 - Quintin Robinson
漂亮的角落案例...... - Thomas Levesque
你可以看到[这个答案] [1] [1]: stackoverflow.com/questions/160118/...    问候 - Nicolocodev


答案:


问题是重载解决:静态方法 Foo() 是一个候选人,它适用 - 只是 选择 它作为最佳匹配将导致错误 - 这正是发生的事情。在考虑所有其他候选者之后,扩展方法仅是重载解决的候选者。在OPs问题的情况下,甚至在错误发生之前都不会考虑扩展方法。


3
2018-03-28 18:44



这就是我如何解释规范的§7.6.5.2。你能解释一下“适用”的含义吗?我很难搞清楚这一点。 - BoltClock♦
扩展方法是重载解析的候选方法 在尝试了非扩展方法之后。编译器可能仍然需要解决重载的扩展方法! - Jon Skeet
谢谢Jon,修好了。 - BrokenGlass
好的,看完规范后,这个解释是有道理的。如果我正确理解§7.6.5.2,则A上的Foo成员查找与实例表达式(“a”)无关。生成的方法组与实例表达式相关联 后 成员查找。起初,对于我来说,为什么静态成员甚至可以在实例上调用函数时成为候选者,这是没有意义的,但我想我知道它是如何工作的。 - Eren Ersönmez
纠正......我在看 7.4会员查询,而不是§7.6.5.2。 - Eren Ersönmez


你不想做什么。该 C#MSDN扩展方法文章 具体说明:

您可以使用扩展方法来扩展类或接口,但不能覆盖它们。永远不会调用与接口或类方法具有相同名称和签名的扩展方法。在编译时,扩展方法的优先级始终低于类型本身中定义的实例方法。

谢天谢地,这是不允许的,因为必须要维护才是可怕的。


编辑:所以人们都说静态方法不是实例方法,这是正确的。但试着这样做:

class A
{
   public static void Foo() {}
   public void Foo() {}
}

由于名称歧义,这不会编译。如果允许您使用扩展方法,那就是会发生什么。它会引入完全相同的歧义。现在,假设一个方法是静态的,一个是实例,那么这应该意味着没有歧义。但在目前的状态下,它确实引入了歧义,这是为什么不允许它的另一个原因。

编辑#2:来自评论@ErenErsonmez:

但是,只要扩展方法与实例方法没有相同的签名,我就不明白它是如何用静态方法引起歧义的

如果您更改扩展方法的签名,它肯定会起作用。所以以下内容将起作用:

class A
        {
            public static void Foo() { }
        }

    static class Ext
    {
        public static void Foo(this A me, int i)
        { }
    }
    class Program
    {
        static void Main(string[] args)
        {
            A a = new A();
            a.Foo(10);

            Console.ReadLine();
        }
    }

所以它看起来更像是一个含糊不清的问题而不是一个与已经存在的方法同名的扩展方法。


5
2018-03-28 18:41



静态方法首先不可覆盖。这篇引言讨论了使用扩展方法语法覆盖实例方法,但问题是为什么在看似实例级的调用中选择静态方法而不是扩展方法。除非静态和实例方法之间的区别在这里无关紧要,否则有人可以详细说明。 - BoltClock♦
@BoltClock我添加了一个编辑。问题不仅限于扩展方法。如果我尝试创建一个具有相同名称和返回类型的实例方法,我将得到一个错误,因为已经有一个具有相同参数类型的方法。扩展方法也会导致此问题。 - Jetti
您的示例中的歧义是有道理的,因为如果您更改实例方法,如'public void Foo(){Foo();}',编译器无法判断它是递归调用还是调用静态方法。但是,只要扩展方法与实例方法没有相同的签名,我就不明白它如何能够引起静态方法的歧义。也许这只是我们现在必须忍受的限制 - 而不是它是一个可怕的限制...... - Eren Ersönmez
@ErenErsonmez如果更改扩展方法的方法签名,它将起作用。我将在编辑中添加它 - Jetti


它来自 这篇MSDN文章 这是出于安全考虑。

我经常听到可以使用扩展方法的担忧   劫持或颠覆现有方法的预期行为。视觉   基本通过尽可能确保实例来确保这一点   方法优于扩展方法。

该语言允许使用扩展方法来创建重载   对于具有不同签名的现有实例方法。这允许   扩展方法用于创建重载,同时防止   来自被覆盖的现有实例方法。如果是扩展方法   存在与实例方法相同的签名,阴影   编译器内置的规则将更喜欢实例   方法,因此消除了扩展方法的可能性   覆盖现有的基类实例功能

这是以VB为重点(并且以实例为重点),但仍然是一般的想法。基本上,扩展方法采用最低优先级,因此方法不能被劫持,并且由于类已经有一个方法签名用于您尝试执行的操作,因此优先级会引发标准扩展方法错误(当尝试从实例对象)。你永远不会有两个具有相同签名的方法,这就是你要求在这里尝试的......并且允许它将是一个安全问题,如上所述。

然后,添加由此创建的混淆,允许它只是一个坏主意。


3
2018-03-28 18:35



本文所讨论的实例方法优先,这是完全合理的。但是,所讨论的示例是关于静态方法优先于扩展方法 在一个对象实例上。 - Eren Ersönmez
@ErenErsonmez但更重要的是,类(静态或实例)中的方法签名优先。我将在答案中进一步澄清这一点。 - Justin Pihony
@ErenErsonmez正如我在编辑我的问题时所述,这不是一个扩展方法问题,而是一个问题,你不能在同一个类中有两个具有相同名称和参数类型的方法。如果允许使用扩展方法,则会为此类创建歧义问题。 - Jetti