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

syn*_*pis 17 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;
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

' System.Collections.Generic.List<System.Collections.Generic.KeyValuePair<string,int>>'不包含' Single' 的定义.

虽然List<T>没有那个方法,但它应该是可见的,因为它是来自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,似乎应该允许扩展方法.

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

Jer*_*vel 26

考虑这种情况:

public class Base
{
    public void BaseMethod()
    {

    }
}

public class Sub : Base
{
    public void SubMethod()
    {

    }
}

public static class Extensions
{
    public static void ExtensionMethod(this Base @base) { }
}
Run Code Online (Sandbox Code Playgroud)

以下是有关此代码的一些有趣断言:

  • 我既不能使用ExtensionMethod()Base不能使用扩展方法Sub.
  • 我不能打电话base.ExtensionMethod()Sub.
  • 可以使用Extensions.ExtensionMethod(this)from Sub调用扩展方法Base.
  • 可以使用this.ExtensionMethod()from 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");
    }
}
Run Code Online (Sandbox Code Playgroud)

有三种方法(不考虑反射)来调用ExtensionMethod(Base)重载:

  • 调用BaseMethod()哪个在子类和extensionmethod之间形成代理.

您可以使用BaseMethod(),base.BaseMethod()this.BaseMethod()对于这一点,因为你现在是在处理一个正常的实例方法这反过来将调用扩展方法.这是一个相当好的解决方案,因为您没有污染公共API,但您还必须提供一个单独的方法来执行本来应该在上下文中可访问的内容.

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

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

Extensions.ExtensionMethod((Base) this);
Run Code Online (Sandbox Code Playgroud)
  • 使用 - 应该是相同的翻译 - 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();将完美地工作.

看起来在文档或编译器中出现了问题,但是结论应该由对此事有更深入了解的人绘制(寻呼Dr. Lippert).

这不是很混乱吗?

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

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

在此输入图像描述

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

在此输入图像描述

一切都没有按预期工作.

也许是一个结论?

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

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

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

  • "不实现它的主要论据是因为它会鼓励人们编写扩展方法而不是实例方法." - 不,这是不支持"ExtensionMethod()"之类的"隐式"扩展方法调用的论据.它与`base.ExtensionMethod()`无关,它不是隐式的.不允许这只是语言规范中的一个错误. (2认同)