为啥我不能从扩展类型的基类调用扩展方法?

Posted

技术标签:

【中文标题】为啥我不能从扩展类型的基类调用扩展方法?【英文标题】:Why can't I call an extension method from a base class of the extended type‏?为什么我不能从扩展类型的基类调用扩展方法? 【发布时间】:2015-03-09 02:58:06 【问题描述】:

我正在尝试通过覆盖索引器来添加在 List<KeyValuePair<string,int>> 中查找元素的功能。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication2

    public class MyList : List<KeyValuePair<string, int>>
    
        public int this[string key]
        
            get
            
                return base.Single(item => item.Key == key).Value;
            
        
    

由于某种原因,编译器抛出此错误:

System.Collections.Generic.List&lt;System.Collections.Generic.KeyValuePair&lt;string,int&gt;&gt;”不包含“Single”的定义。

虽然List&lt;T&gt; 确实没有该方法,但它应该是可见的,因为它是System.Linq 命名空间(包括在内)的扩展方法。明明使用this.Single可以解决问题,但是为什么通过base访问会出错呢?

C# 规范的第 7.6.8 节说

base.I 出现在类或结构中时,I 必须表示该类或结构的基类的成员。

这似乎阻止了通过base 访问扩展方法。但是它也说

在绑定时,base.Ibase[E] 形式的基本访问表达式的求值方式与编写 ((B)this).I((B)this)[E] 完全相同,其中 B 是类的基类或结构发生在其中。因此,base.Ibase[E] 对应于 this.Ithis[E],除了 this 被视为基类的实例。

如果base.I((B)this).I 一样,那么这里似乎应该允许扩展方法。

谁能解释这两个陈述中明显的矛盾?

【问题讨论】:

你为什么不用字典? 您的属性必须具有int 类型,不是吗? 使用this 而不是base。该属性是Key 而不是NameKeyValuePair 没有 Value 的设置器。该属性应返回类型int ^这是正确的答案,但是为什么你必须使用这个而不是base? 看看***.com/questions/9945951/… 【参考方案1】:

考虑这种情况:

public class Base

    public void BaseMethod()
    

    


public class Sub : Base

    public void SubMethod()
    

    


public static class Extensions

    public static void ExtensionMethod(this Base @base)  

以下是关于这段代码的一些有趣的断言:

我无法使用ExtensionMethod()BaseSub 调用扩展方法。 我无法从Sub 呼叫base.ExtensionMethod()。 我可以使用Extensions.ExtensionMethod(this)SubBase 调用扩展方法。 我可以SubBase 使用this.ExtensionMethod() 调用扩展方法。

这是为什么?

我没有决定性的答案,部分原因是可能没有答案:正如您在this thread 中看到的那样,如果您想调用它,您必须添加this.扩展方法样式。

当您尝试从它所在的类型(或 - 因此 - 从派生自扩展方法中使用的类型的类型)中使用扩展方法时,编译器不会意识到这一点,并会尝试将其作为不带任何参数的静态方法调用。

正如答案所述:他们 [语言设计者] 认为从类型中支持隐式扩展方法(给野兽命名)并不是一个重要的用例场景,因为它会鼓励真正应该使用的扩展方法实例方法,它被认为是不必要的。

现在,很难找出幕后到底发生了什么,但从一些玩耍中我们可以推断出base.X() 对我们没有帮助。我只能假设base.X 在基类的上下文中以X() 而不是this.X() 执行其虚拟调用。

想从子类调用基类的扩展方法怎么办?

坦率地说,我还没有找到任何真正优雅的解决方案。考虑这种情况:

public class Base

    protected void BaseMethod()
    
        this.ExtensionMethod();
    


public class Sub : Base

    public void SubMethod()
    
        // What comes here?
    


public static class Extensions

    public static void ExtensionMethod(this Base @base) 
     
        Console.WriteLine ("base");
    

    public static void ExtensionMethod(this Sub sub) 
    
        Console.WriteLine ("sub");
    

有 3 种方式(抛开反射)调用ExtensionMethod(Base) 重载:

调用BaseMethod(),它在子类和扩展方法之间形成一个代理。

您可以为此使用BaseMethod()base.BaseMethod()this.BaseMethod(),因为现在您只是在处理一个普通的实例方法,而该方法又会调用扩展方法。这是一个相当不错的解决方案,因为您不会污染公共 API,但您还必须提供一个单独的方法来执行应该在上下文中首先可以访问的操作。

使用扩展方法作为静态方法

您还可以使用原始方式编写扩展方法,跳过语法糖并直接编译它。现在您可以传入一个参数,这样编译器就不会感到困惑。显然,我们将传递当前实例的强制转换版本,因此我们的目标是正确的重载:

Extensions.ExtensionMethod((Base) this);
使用base.ExtensionMethod()的-应该是相同的翻译-

这受到@Mike z 关于语言规范的评论的启发,该评论如下:

在绑定时,base.Ibase[E] 形式的基本访问表达式的求值方式与编写 ((B)this).I((B)this)[E] 完全相同,其中 B 是类的基类或结构发生在其中。因此,base.Ibase[E] 对应于this.Ithis[E],除了this 被视为基类的一个实例。

规范字面上说base.I 将被调用为((B) this).I。但是在我们的情况下,base.ExtensionMethod(); 会抛出编译错误,而((Base) this).ExtensionMethod(); 会正常工作。

文档或编译器中似乎有问题,但该结论应该由对此事有更深入了解的人得出(寻呼 Lippert 博士)。

这不是很混乱吗?

是的,我会说是。这有点像 C# 规范中的一个黑洞:几乎所有东西都能完美运行,但突然之间你不得不跳过一些障碍,因为在这种情况下编译器不知道在方法调用中注入当前实例。

事实上,intellisense 也对这种情况感到困惑:

我们已经确定该调用永远不会起作用,但智能感知认为它可以。还要注意它如何在名称后面添加“using PortableClassLibrary”,表示将添加一个using 指令。这是不可能的,因为当前的命名空间实际上是PortableClassLibrary。但当然,当您实际添加该方法调用时:

一切都没有按预期工作。

也许是一个结论?

主要结论很简单:如果扩展方法的这种小众用法能够得到支持,那就太好了。不实现它的主要论点是因为它会鼓励人们编写扩展方法而不是实例方法。

这里明显的问题当然是您可能并不总是可以访问基类,这使得扩展方法是必须的,但根据当前的实现,这是不可能的。

或者,正如我们所见,不可能使用可爱的语法。

【讨论】:

" 不实现它的主要论据是因为它会鼓励人们编写扩展方法而不是实例方法。" -- 不,这是不支持像 @ 这样的“隐式”扩展方法调用的论据987654365@。它与base.ExtensionMethod() 无关,这不是隐含的。不允许这样做只是语言规范中的一个错误。 其中一个实例是Microsoft.AspNet.Identity.UserManager,其中所有实例方法都是Async,MS 已将非异步方法创建为扩展方法。鼓励您扩展UserManager,但您的子类不能调用非异步方法。您既不能访问基类也不能访问扩展方法。

以上是关于为啥我不能从扩展类型的基类调用扩展方法?的主要内容,如果未能解决你的问题,请参考以下文章

定义扩展类型(翻译)

测试扩展另一个类的抽象类

在值类型上定义的扩展方法不能用于创建委托 - 为啥不呢?

为啥我不能正确使用扩展类的方法?

使用学说扩展基类,我的基类是不是有 1:n 关联

确定子类是不是具有在 Python 中实现的基类方法