是否可以在可以修改的类上使用扩展方法?

本文关键字:扩展 方法 修改 是否 | 更新日期: 2023-09-27 17:49:42

我最近一直在考虑使用扩展方法在我控制的类(即,在同一程序中,我可以修改)上实现助手实用程序的想法。其背后的基本原理是,很多时候,这些helper实用程序用于非常特定的场景,并且不需要访问类的内部值。

例如,假设我有一个StackExchange类。它有PostQuestion Search AnswerQuestion这样的方法。

现在,如果我想手动计算我的声誉,以确保StackOverflow没有欺骗我。我将实现如下代码:

int rep=0;
foreach(var post in StackExchangeInstance.MyPosts)
{
  rep+=post.RepEarned;
}

我可以在StackExchange类中添加一个方法,但它不需要任何内部结构,并且它只在程序的一个或两个其他部分中使用。

现在想象一下,如果您有10或20个这些特定的帮助器方法。在特定场景中确实有用,但绝对不是一般情况。我的想法是改变像

这样的东西
public static RepCalcHelpers
{
  public static int CalcRep(StackExchange inst){ ... }
}

namespace Mynamespace.Extensions.RepCalculations
{
  public static RepCalcExtensions
  {
    public static int CalcRep(this Stackexchange inst){...}
  }
}

注意命名空间。理想情况下,我将使用它来对特定场景中的扩展方法进行分组。例如,"repcounts"、"Statistics"等

我试着搜索是否听说过这种类型的模式,并且没有发现任何证据表明扩展方法被用于除了你不能修改的类之外的任何东西。

这种"模式"有什么缺点?我应该坚持继承或组合,还是只使用一个好的静态helper类?

是否可以在可以修改的类上使用扩展方法?

我会阅读框架设计指南中关于扩展方法的部分。这是第二版的一位作者的一篇文章。您正在描述的用例Phil Haack认为(专门的助手方法)是扩展方法的一种有效用法,但缺点是需要额外的API知识才能找到那些"隐藏"的方法。

在那篇文章中没有提到,但在书中推荐的是,扩展方法与扩展类放在一个单独的命名空间中。否则,它们总是会带着智能感出现,没有办法把它们关掉。

我想我在别的地方见过这种模式。它可能会很混乱,但也很强大。这样就可以在库中提供一个类,在单独的名称空间中提供一组扩展方法。然后,使用您的库的任何人都可以选择使用您的扩展方法导入名称空间,或者提供他们自己的扩展方法。

如果您有一些仅用于单元测试的扩展方法(例如,比较两个对象是否在某种意义上相等,您只需要进行单元测试),则可以使用此模式。

您似乎正在进行扩展方法等同于公共实例方法的比较。真的不是。

扩展方法只是一个公共的静态实用程序方法,它恰好具有更方便调用的语法。

所以首先我们要问自己,这个方法作为类本身的实例方法是否合适,还是作为外部类的静态方法更合适。事实上,类的很少用户需要这个功能,因为它是高度本地化的,并且不是类本身执行的真正行为,而是由外部实体在类上执行的行为,这意味着它是静态的。主要缺点是,如果有人有User并且想要重新计算他们的代表,那么它的行为可能更难以找到。现在,在这个特殊情况下,它有点处于观望状态,您可以走另一条路,但我倾向于静态方法。

现在我们已经决定它应该是静态的,它是否应该是一个扩展方法是一个完全不同的问题。这是更主观的,属于个人偏好领域。这些方法可能被链接吗?如果是这样,扩展方法的链接要比对静态方法的嵌套调用好得多。它可能在使用它的文件中被大量使用吗?如果是,扩展方法可能会稍微简化代码,如果不是,它就没有多大帮助,甚至会造成伤害。对于这个简单的例子,我可能会说我个人不会这样做,但如果有人这样做了,我也不会有任何问题(毕竟,您仍然可以使用扩展方法,就好像它是一个常规的公共静态方法语法一样)。对于一个非玩具的例子,这主要是一个具体情况的决定。关键的一点是要小心你扩展了什么类,并且要问自己,用户是否愿意打乱类型的智能感知,只是为了更方便地调用一个方法(这又回到了它在每个文件中使用了多少)。

同样值得一提的是,在一些边缘情况下,扩展方法可能比实例化方法更强大。特别是通过使用类型推断。使用常规实例方法很容易接受类型或该类型的任何子类型,但有时返回传递进来的类型而不是父类型是有用的。这在流畅api中尤为常见。这不是一个很常见的例子,只是和你的问题有一点关系,所以我就不展开了。

当你的类实现了一个接口,并且你想避免在其他"未来"实现相同接口的类上实现相同的方法时,扩展方法可能非常有用。例如,StackExchange实现了IStackExchange, ProgrammersExchange也实现了IStackExchange。您的示例扩展方法对于只实现一次CalcRep很有用,而不必在两个类上重新实现它。这正是静态Enumerable类中出现所有扩展方法的原因。

除此之外,我看不出有什么令人信服的理由在一个你已经可以修改的类上使用扩展方法。如果有的话,它的缺点是在重载解析过程的后期被考虑。