覆盖扩展方法

我一直在考虑使用扩展方法作为抽象基类的替代。 扩展方法可以提供默认function,并且可以通过在派生类中放置相同签名的方法来“覆盖”。

我不应该这样做的原因?

另外,如果我有两个具有相同签名的扩展方法,使用哪一个? 有没有办法确定优先权?

我同意迈克尔的观点。 基类应该包含所有基本function扩展方法显然应该扩展基本function。 在像Ruby这样的动态语言中,通常使用扩展方法来提供附加function而不是使用子类。 基本上,扩展方法是使用子类替换,而不是使用基类替换。

我见过的唯一例外是,如果你有多个类型具有不同的类层次结构(比如winform控件),你可以创建每个类的子类,它们都实现和接口,然后扩展该接口,从而给出“基础”一组不同控件的function,而不扩展Control或Object等所有内容。

编辑:回答你的第二个问题

我认为编译器会为你捕获这个。

通常,您不应通过扩展方法提供“基本”function。 它们只应用于“扩展”类function。 如果您可以访问基类代码,并且您尝试实现的function在逻辑上是inheritance层次结构的一部分,那么您应该将它放在抽象类中。

我的观点是,仅仅因为你并不意味着你应该这样做 。 当普通的OO编程无法为您提供合理的解决方案时,通常最好只使用优秀的老式OOP并使用更新的语言function。

这绝对是一个坏主意。 扩展方法是静态绑定的,这意味着,除非您在编译时类型为子类型的对象上调用覆盖,否则您仍将继续调用扩展方法。 再见多态性。 这个页面很好地讨论了扩展方法的危险。

这里的所有答案都说“你不能”,这是真实的。 大多数人补充说“你不应该”。 我想说的是你应该能够 – 这可能是一种小小的安慰。

拿一个痛苦的现实世界的例子:如果你不幸使用新的MVC框架,并且你的视图代码在整个地方使用了一些HtmlHelper扩展方法,并且你想要覆盖它的默认行为……那么呢?

你是SOL,就是这样。 即使你做了“OOP事情” – 派生自HtmlHelper,更改你的基本视图类,用DerivedHtmlHelper的实例替换’Html’对象实例,并在其中定义一个明确的’Foo’方法 – 即使你做了所有那个,调用’Html.Foo’ 仍然会调用原始的扩展方法而不是方法。

这太令人惊讶了! 毕竟,只有当对象还没有方法时,才会应用扩展方法! 这里发生了什么?

好吧,这是因为扩展方法是一个静态function。 也就是说,当看到’Html.Foo’时,编译器会查看静态类型’Html’。 如果它有一个’Foo’方法,它会像往常一样调用。 否则,如果’SomeClass’提供’Foo’扩展方法,它会将表达式转换为’SomeClass.Foo(Html)’。

您期望的是编译器会考虑对象的动态类型。 也就是说,生成的(伪)代码会读取’Html.HasMethod(“Foo”)? Html.Foo():SomeClass.Foo(Html)’。

这当然会导致在每个扩展方法调用中使用reflection的成本。 所以,你可以期望你可以编写类似’static void Foo( virtual this HtmlHelper html)’来显式请求编译器插入运行时检查。 将其称为“虚拟扩展方法”。

然而,在他们有限的预算和无限智慧中,C#语言设计师只选择了更有效,更受限制的替代方案。 当我需要覆盖HtmlHelper的默认行为时,这让我仍然是SOL 🙁

你有充分的理由不应该这样做。 第一个是,您无法保证如何调用您的扩展程序:

MyExtensions.AMethod(myObj) 

要么

 myObj.AMethod() 

第二个只是第一个的语法糖。

你的建议与语言特征的精神背道而驰。 扩展方法绝对不是面向对象的。 然而,您正在尝试实现面向对象的技术。 不要使用扩展方法。

它们在语义上是不同的操作。 例如,多态可能无法像抽象基类那样工作。

作为一般规则,使用任何语言工具来实现它的目的。 扩展方法不是inheritance的替代,它们是一种使用(通常)它已经可见的接口来扩展类的function的技术。

如果您不需要唯一的派生类,您可能需要考虑前面的多态性。

这篇MSDN 文章中的一些通用指南:

  1. 在类中定义方法后,将不再执行共享该方法名的该类的任何扩展方法。

  2. 扩展方法由命名空间加载。 要允许其他扩展方法的用户,您应该避免将扩展方法放在与要扩展的类相同的命名空间中。

如果各种依赖程序集使用相同的库,每个需要针对库中的类的自定义函数,则可以在该程序集中的名称空间中声明特定于依赖程序集的扩展。 不依赖于该组件的其他组件将不具有该扩展。

虽然这不是多态,但您可能不希望使用多态,因为它需要所有派生类来实现它们的覆盖。

换句话说,如果您具有特定的变体逻辑的衍生物,请使用多态。 如果您只是在特定情况下需要自定义函数,否则可能会造成混淆或繁琐,扩展方法并不是一个坏主意。

另外,请参阅JoshRivers的答案,该答案显示如何在单独的命名空间内使用类中的命名空间覆盖扩展方法。

首先,将[new]检查为方法修饰符会很好 – 它应该给出相同的perf和方法分离,但是麻烦要少得多。

如果在那之后你还在思考相同的技巧,这里有一些主要的选择 – 没有涉及意识形态,只需列出你需要注意的方法:-)

首先,您可能必须限制对模板化函数参数的所有使用,以保证确定性函数选择,因为此技巧将创建与CLR保证跨函数调用边界的确定性绑定优先级完全相反的情况。

如果您仍然拥有抽象基类并且您拥有所有代码,那么只需将一个基本方法“转换”为扩展,我们就不会为您买任何东西而只会损失一个通用接口,而这个接口仍将存在于其他所有内容中,包括vtable的费用。

如果你的目标是将抽象类转换为具体的,消除所有虚拟方法的性能原因,并使用具体的基类和所有衍生物专门在模板中(甚至转换为结构),那么这个技巧可能会给你带来一些性能。 您只需要意识到,如果不进行重构,您将无法返回基于接口的使用。 您还需要使用签名中的基类测试非模板函数中的调用顺序,如果您有多个dlls-s,则需要格外小心。 尝试使用[new]运算符,看看它是否更好。

此外,您必须为每个派生类编写单独的方法实现,即使它们具有完全相同的function,因此如果您有许多派生类,那么您正在查看大量代码重复。 除非你回到虚拟方法并在inheritance中引入另一个层,否则即使你使用[new],这个部分也会遇到你 – 这当然会取消所有的perf增益。

添加一个链接到另一个具有替代答案而不是“你不能”的SO问题。 更多’你不能……除非’。

如何覆盖现有的扩展方法