为什么我不能从扩展类型的基类调用扩展方法?
我正在尝试通过覆盖索引器来添加查找List<KeyValuePair>
的元素的function。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ConsoleApplication2 { public class MyList : List<KeyValuePair> { public int this[string key] { get { return base.Single(item => item.Key == key).Value; } } } }
由于某种原因,编译器抛出此错误:
‘
System.Collections.Generic.List<System.Collections.Generic.KeyValuePair>
‘不包含’Single
‘的定义。
虽然List
确实没有该方法,但它应该是可见的,因为它是System.Linq
命名空间(包含在内)中的扩展方法。 显然使用this.Single
解决了这个问题,但为什么通过base
访问是一个错误?
C#规范第7.6.8节说
当
base.I
出现在类或结构中时,I
必须表示该类或结构的基类的成员。
这似乎阻止了通过base
访问扩展方法。 不过它也说
在绑定时,
base.I
和base[E]
forms的基本访问表达式的计算方式与它们的编写完全相同((B)this).I
和((B)this)[E]
,其中B
是构造发生的类或结构的基类。 因此,base.I
和base[E]
对应于this.I
和this[E]
,除了this
被视为基类的实例。
如果base.I
就像((B)this).I
那么((B)this).I
似乎应该允许扩展方法。
任何人都可以解释这两个陈述中明显的矛盾吗?
考虑这种情况:
public class Base { public void BaseMethod() { } } public class Sub : Base { public void SubMethod() { } } public static class Extensions { public static void ExtensionMethod(this Base @base) { } }
以下是有关此代码的一些有趣断言:
- 我无法从
Base
和Sub
使用ExtensionMethod()
来调用扩展方法。 - 我不能从
Sub
调用base.ExtensionMethod()
。 - 我可以使用
Sub
和Base
Extensions.ExtensionMethod(this)
来调用扩展方法。 - 我可以使用
Sub
和Base
this.ExtensionMethod()
调用扩展方法。
为什么是这样?
我没有确定的答案,部分是因为可能没有答案:你可以在这个post中读到,你必须加上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"); } }
有三种方法(抛开reflection)来调用ExtensionMethod(Base)
重载:
- 调用
BaseMethod()
,它在子类和extensionmethod之间形成代理。
你可以使用BaseMethod()
, base.BaseMethod()
和this.BaseMethod()
,因为现在你只是处理一个普通的实例方法,而这个方法又会调用扩展方法。 这是一个相当好的解决方案,因为您没有污染公共API,但您还必须提供一个单独的方法来执行本来应该在上下文中可访问的内容。
- 使用扩展方法作为静态方法
您还可以通过跳过语法糖并直接编译为编译的方法来使用编写扩展方法的原始方法。 现在你可以传入一个参数,这样编译器就不会感到困惑了。 显然,我们将传递当前实例的转换版本,因此我们正在针对正确的重载:
Extensions.ExtensionMethod((Base) this);
- 使用 – 什么应该是相同的翻译 –
base.ExtensionMethod()
这是受@Mike z关于语言规范的评论的启发,该语言规范如下:
在绑定时,
base.I
和base[E]
forms的基本访问表达式的计算方式与它们的编写完全相同((B)this).I
和((B)this)[E]
,其中B
是构造发生的类或结构的基类。 因此,base.I
和base[E]
对应于this.I
和this[E]
,除了this
被视为基类的实例。
该规范字面上说base.I
将被调用为((B) this).I
。 但是在我们的情况下, base.ExtensionMethod();
将抛出一个编译错误((Base) this).ExtensionMethod();
会很完美。
在文档或编译器中看起来有些不对劲,但这个结论应该由对此事有更深入了解的人绘制(寻呼Dr. Lippert)。
这不是很困惑吗?
是的,我会说是的。 它有点像C#规范中的黑洞:实际上一切都运行得很完美,但突然之间你必须跳过一些箍,因为编译器不知道在这种情况下在方法调用中注入当前实例。
事实上,intellisense也对这种情况感到困惑:
我们已经确定该呼叫永远不会起作用,但是intellisense认为它可能会起作用。 还要注意它如何在名称后添加“ using PortableClassLibrary ”,表示将添加using
指令。 这是不可能的,因为当前的命名空间实际上是PortableClassLibrary
。 但当然,当您实际添加该方法调用时:
一切都没有按预期工作。
也许是一个结论?
主要结论很简单:如果支持扩展方法的这种利基使用,那将是很好的。 不实现它的主要论据是因为它会鼓励人们编写扩展方法而不是实例方法。
这里显而易见的问题当然是您可能无法始终访问基类,这使得扩展方法成为必需,但是当前的实现无法实现。
或者,正如我们所见,不太可能使用可爱的语法。