覆盖扩展方法

时间:2009-01-23 19:05:50

标签: c#

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

我不应该这样做吗?

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

9 个答案:

答案 0 :(得分:12)

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

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

答案 1 :(得分:8)

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

答案 2 :(得分:4)

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

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

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

这太令人惊讶了!毕竟,只有当对象还没有方法时,才会应用扩展方法!这是怎么回事?

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

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

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

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

答案 3 :(得分:3)

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

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

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

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

答案 4 :(得分:2)

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

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

答案 5 :(得分:2)

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

MyExtensions.AMethod(myObj)

myObj.AMethod()

第二种只是第一种语法糖。

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

答案 6 :(得分:2)

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

此MSDN article中的一些一般准则:

  1. 一旦在类中定义了一个方法,就可以使用任何扩展方法 共享该方法名称的类将不再执行。

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

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

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

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

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

答案 7 :(得分:0)

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

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

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

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

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

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

答案 8 :(得分:0)

添加指向另一个SO问题的链接,该问题的答案不是“您不能”。更多'你不能......除非'。

How to override an existing extension method