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

时间:2015-01-11 02:37:11

标签: c# linq inheritance extension-methods

我尝试通过覆盖索引器来添加查找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;
            }
        }
    }
}

由于某种原因,编译器抛出了这个错误:

  

&#39; System.Collections.Generic.List<System.Collections.Generic.KeyValuePair<string,int>>&#39;不包含&#39; Single&#39;。

的定义

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

C#规范的第7.6.8节说

  

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

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

  

在绑定时,base.Ibase[E]形式的基本访问表达式的计算方式与((B)this).I((B)this)[E]一样,其中{{1}是构造发生的类或结构的基类。因此,Bbase.I对应base[E]this.I,但this[E]被视为基类的实例。

如果thisbase.I类似,那么此处似乎应该允许使用扩展方法。

有谁可以解释这两个陈述中明显的矛盾?

1 个答案:

答案 0 :(得分:24)

考虑这种情况:

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()Base中的Sub来调用扩展方法。
  • 我无法从base.ExtensionMethod()致电Sub
  • 可以<{1}}和Extensions.ExtensionMethod(this)使用Sub来调用扩展方法。
  • 可以<{1}}和Base使用this.ExtensionMethod()来调用扩展方法。

为什么会这样?

我没有得出确凿的答案,部分原因是可能没有答案:您可以在this thread中阅读, 添加Sub你想用扩展方法风格来调用它。

当你尝试使用它所在类型的扩展方法时(或 - 因此 - 来自扩展方法中使用的类型派生的类型),编译器没有意识到这将并尝试将其称为静态方法,不带任何参数。

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

现在,很难找到正是在幕后发生的事情,但从一些游戏中我们可以推断Base对我们没有帮助。我只能假设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"); } } ,它在子类和extensionmethod之间形成代理。

您可以使用ExtensionMethod(Base)BaseMethod()BaseMethod(),因为现在您只是处理普通的实例方法,而后者又会调用扩展方法。这是一个相当好的解决方案,因为您不会污染公共API,但您还必须提供一个单独的方法来执行本来应该在上下文中可访问的内容。

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

您还可以使用原始的方式编写扩展方法,方法是跳过语法糖并直接进行编译。现在你可以传入一个参数,这样编译器就不会感到困惑。显然,我们会通过当前实例的转换版本,以便我们针对正确的重载:

base.BaseMethod()
  • 使用this.BaseMethod()
  • 的 - 应该是相同的翻译

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

  

在绑定时,Extensions.ExtensionMethod((Base) this); base.ExtensionMethod()形式的基本访问表达式的计算方式与base.Ibase[E]一样,其中{{1}是构造发生的类或结构的基类。因此,((B)this).I((B)this)[E]对应Bbase.I,但base[E]被视为基类的实例。

该规范字面上说this.I将被调用为this[E]。但是在我们的情况下,this会抛出编译错误,而base.I将完美无缺。

在文档或编译器中看起来有些不对劲,但这个结论应该由对此事有更深入了解的人绘制(分页Lippert博士)。

这不是很混乱吗?

是的,我会说是的。它有点像C#规范中的黑洞:实际上一切都运行得很完美,但突然之间你必须跳过一些箍,因为编译器在这种情况下不知道在方法调用中注入当前实例。 / p>

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

enter image description here

我们已经确定该呼叫永远不会起作用,但智能感知可能会这样。另请注意它如何使用PortableClassLibrary &#34;添加&#34; 在名称后面,表示将添加((B) this).I指令。这是不可能的,因为当前的命名空间实际上是base.ExtensionMethod();。但当然,当您实际添加该方法调用时:

enter image description here

并且一切都没有按预期工作。

也许是个结论?

主要结论很简单:如果支持扩展方法的这种利基使用,那就太好了。不实现它的主要理由是因为它会鼓励人们编写扩展方法而不是实例方法。

这里显而易见的问题当然是您可能无法始终访问基类,这使得扩展方法成为必需,但目前的实现却无法实现。

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