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

24

我正在尝试通过重写索引器来添加在 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<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 那么似乎扩展方法应该在这里允许。

有人能解释这两个语句之间的表面矛盾吗?


你的属性必须是 int 类型,对吗? - Mark Shevchenko
2
使用this而不是base。属性是Key而不是NameKeyValuePair没有Value的setter。该属性应返回类型为int - Mike Zboray
1
这是正确的答案,但为什么你必须使用此而不是 base? - synepis
@sklitzz 我正在努力回答。我认为规范在这里使用基础 vs. this 存在矛盾。 - Mike Zboray
@mike z:感谢您详细解释/编辑我的问题。今天我学到了关于C#非常有趣的东西。 - synepis
显示剩余3条评论
2个回答

33
考虑以下情况:

Consider this situation:

public class Base
{
    public void BaseMethod()
    {

    }
}

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

    }
}

public static class Extensions
{
    public static void ExtensionMethod(this Base @base) { }
}

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

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

为什么会这样?

我没有确凿的答案,部分原因是可能不存在一个答案:正如你可以在这个线程 中读到的那样,如果你想以扩展方法的形式调用它,则必须添加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(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#规范中的黑洞:实际上,几乎所有东西都能无缝工作,但在这种情况下,您必须跳过某些障碍,因为编译器不知道在此场景中注入当前实例到方法调用中。

事实上,智能感知对这种情况也很困惑:

enter image description here

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

enter image description here

一切都没有按预期工作。

或许有一个结论?

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

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

或者,正如我们所看到的,无法使用可爱的语法。


2
“不实现它的主要论点是因为这会鼓励人们编写扩展方法而不是实例方法。”-- 不,这是不支持“隐式”扩展方法调用(如ExtensionMethod())的论点。这与base.ExtensionMethod()无关,后者并不是隐式的。不允许使用它只是语言规范中的一个错误。 - Jim Balter
一个例子是 Microsoft.AspNet.Identity.UserManager,其中所有实例方法都是 Async 的,而 MS 则创建了非 Async 方法作为扩展方法。鼓励您扩展 UserManager,但您的子类不能调用非 Async 方法。您既无法访问基类也无法访问扩展方法。 - Dr Rob Lang
1
过去5年中有什么变化吗? - T_01

-1
尝试将实例转换为其基类:
((BaseClass)this).ExtensionMethod()

适用于您的代码:

public class Base
{
    public void BaseMethod()
    {

    }
}

public static class BaseExtensions
{
    public static void ExtensionMethod(this Base baseObj) { }
}

public class Sub : Base
{
    public void SubMethod()
    {
              ( (Base) this).ExtensionMethod();
    }
}


应用于您的代码:public class Base { public void BaseMethod() {}}public class Sub : Base { public void SubMethod() { ( (Base) this).ExtensionMethod(); } }public static class Extensions { public static void ExtensionMethod(this Base @base) { } } - Pedro Herrarte

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接