扩展方法及其在软件工程中的意义(或者它真的是一个好主意吗?)

时间:2009-07-27 06:22:47

标签: c#-3.0 extension-methods

是的,所以MS介绍了Extension Methods for C# 3.0。基本上,它允许您向现有类添加新方法,而无需从中继承或更改其实现。我的脑海里立刻响起了警钟。

1)您开始为库或标准库类实现自己的扩展方法。现在它不再标准化了。

2)在OOP中,您可以通过继承或组合定义新类型。实际上扩展方法是什么呢?同样,在使用扩展方法时,没有定义明确的关系。

3)扩展方法是否可以在项目之间移植?如果存在两种方法的扩展方法怎么办?

扩展方法也是一大堆蠕虫值得避免的吗?我可以看到它有优点,因为你输入较少,但不知何故看起来会导致不好的练习。

评论

7 个答案:

答案 0 :(得分:9)

扩展方法不是添加到类型中。它们只是语法糖。你不能访问私人的东西。扩展方法就像任何使用类型的静态方法一样。

他们能做的最糟糕的事情是损害可读性并误导人们。除了语法之外,它们是不存在的。

更新(重新评论):

这正是我从“误导人”和“伤害可读性”的意思。幸运的是,Visual Studio在区分扩展方法(使用IntelliSense列表中的图标和工具提示)方面非常有用。就个人而言,我有点偏向于在框架的任意类中添加一堆扩展方法(除了sealed类,其中扩展方法可以很有意义),除非它们为特定项目提供了重要的好处。我更喜欢保持扩展方法主要用于继承层次结构的顶级接口和类。这将使开发人员只使用它们适用于框架中的许多情况,而不是将任何实用程序方法作为扩展(IMO,这是在整个命名空间中自动发现扩展方法的直接工件。我真的希望C#需要你添加针对特定using的{​​{1}}指令,而不是自动导入class中的所有扩展程序。

根据我的经验,如果你没有过度使用扩展方法,通过使用几次(我见过一些人这样做)扩展的实用方法,它很少是一个维护问题。如果明智地使用它将提高可读性。

使用扩展方法来破坏类并不是一件好事。在极端情况下,它可能是危险的。有人可能会通过向类添加一个与现有扩展名相同的方法来引入名称冲突问题。这可能会破坏以前称为扩展名的代码片段,现在正在调用新方法 在我看来,这是与扩展方法相关的最大问题。这使得扩展的良好命名非常重要。

答案 1 :(得分:5)

不,这不是一堆值得避免的蠕虫。它可以简化生活,IMO允许您将类型的核心操作与“辅助”方法分开,这些方法可以仅从这些核心操作中实现。我一直试图以这种方式使用扩展方法,即使它们不是非常必要的(即我有一个基类来放入它们,我控制代码)。

当导入包含扩展方法的相应命名空间时,您需要了解扩展方法只是一种使静态方法调用看起来像实例方法的方法。特别是:

  • 向类型添加方法,因此它不会使标准库“非标准”。这只是意味着如果你有可用的扩展方法,你可以更容易地使用这些库。
  • 用扩展方法定义的显式关系:扩展方法声明它与第一个参数的类型“扩展”的类型。它不是组合或聚合,但我不明白为什么这是一个问题。如果某些东西是有用的并且实际上并没有使设计变得更糟,那么它是否是传统OO的一部分是否重要?
  • 是的,扩展方法可以在项目之间移植。与往常一样,如果同时可以看到两个具有相同名称的扩展方法,则会有相应的规则。

接口上的扩展方法非常很好,允许简单的方法链接等。我宁愿看到扩展方法调用而不是Java中的显式静态调用:

// Explicit helper method call
Collections.sort(collection);
// With extensions, this could be:
collection.sort();

但有一些缺点:

  • 它不允许派生类型以更有效的方式覆盖实现。当然派生类型可以声明一个具有相同签名的方法,但只有在编译时类型合适时才会使用。
  • 在C#中发现扩展方法的方式很痛苦。我在其他地方咆哮过......
  • 使用动态类型时无法使用扩展方法。

总的来说,我认为它们很可爱 - 当适度使用时,它们可以发挥重要作用。我当然不想在没有LINQ的情况下使用LINQ。

答案 2 :(得分:5)

扩展方法根本不修改现有的类,它们只是“语法糖”或用于将静态方法附加到类的编译器技巧。您可以通过在另一个类中创建一个普通的静态实用程序方法来实现相同的目的,该类将所需的类作为该方法的第一个参数。我看到了关于向类添加“方法”并因此对其进行去标准化的观点,但这并不是真正发生的事情,我实际上发现扩展方法直观且方便地向现有类添加特定于域或实用程序的方法否则将无法修改。

答案 3 :(得分:5)

您可以关闭闹铃。扩展方法只对语法产生非常小的差异,而没有其他方法。而不是写:

SomeClass.SomeStaticMethod(someVar, someArg);

你写道:

someVar.SomeStaticMethod(someArg);

它只是交换排序并消除了类名限定符。否则,它是相同的。这样做的价值在于您可以键入变量名称,IDE可以在智能感知功能中为您提供有用的方法,并且您可以从左到右阅读代码,因此“嵌套”函数调用变得更具可读性。

您所有的担忧同样适用于静态方法 - 因此不值得担心。

<强>更新

扩展方法“假装做魔术”吗?只有你认为交换一些语法才是神奇的!

你更有可能习惯于以某种方式看到写的东西,所以看到它们写成“向后”是令你惊讶的。但是从其他语言的角度来看,现在是正确的方法。

有些语言允许任何非实例方法可选地使用方法名称前的第一个参数写入,如果省略了点,那么它是一种支持中缀运算符(如a + b)的简洁方法,而不必执行此操作什么特别的。

实例方法是启用此语法的一种方法,但如果你很乐意没有它,那么为了真正一致,为什么不要求像这样调用实例方法呢?

trimmed = string.Trim(str);

这类似于F#的做法。毕竟,string的实例方法可以被视为具有类型string的第一个参数。您通常不会将其视为参数,但它的工作方式大致相同,并且统一语法也有优势。

实例方法可以访问其定义的类的私有成员,而扩展方法则不能。但是,派生类中的实例方法无法访问基类的私有数据。无论如何,打电话者为什么要关心这个?

<强>注意事项

我不想给人的印象是扩展方法的所有用途都有意义。例如,有没有充分的理由在object上创建扩展方法,或在无约束类型T上创建泛型扩展方法?这种事情几乎可以在任何情境中通过智能感知来提出,并且会变成类似于语言扩展的东西,例如With

然后是关于扩展方法是否应允许其第一个参数为null的争论。就个人而言,只要方法的名称明确这一点,我认为这很好,所以我认为string.IsNullOrEmpty可以作为一种扩展方法。但是其他人对此更加激进,我不在乎,所以我会采取“在罗马时”的态度。这是另一个例子:

public static void DisposeIfNotNull(this IDisposable d)
{
    if (d != null)
        d.Dispose();
}

该名称清楚地表明它包含了一个空检查(这就是它的全部要点)。

答案 4 :(得分:2)

我将其视为实现装饰器模式的便捷方式。扩展方法允许您通过向其添加方法来扩展类型。当然,如果您将扩展类放在可以共享的单独程序集中,它们可以在项目之间移植。

答案 5 :(得分:1)

我想提出一个问题,你说的一点。扩展方法不允许您向现有类添加方法。它们允许您给现有类添加方法的外观。区别是微妙但非常重要。

现在反对你提出的观点

  1. 我不相信这是真的。关于图书馆的标准化没有任何改变。使用扩展方法的人将看到对库的扩充。它是否改变了库的标准在很大程度上取决于你在方法中的作用。

  2. 这只是一种用方法扩充类型而不改变原始契约的方法。

  3. 我不确定便携式是什么意思,但它可能高度依赖于您的实施。如果存在两个具有相同名称的扩展方法,则它们将通过重载解析并且将适当地引发错误。

  4. 使用和定义扩展方法只是静态方法的语法糖。它允许我使用特定于我的问题域的方法来定制缺少更好单词的类型,或者只是原始框架(IEnumerable<T>.ForEach for example)中缺少的东西。

    我没有发现任何关于他们邪恶的说法令人信服,因此我会继续使用它们,因为我觉得它们很有成效。

答案 6 :(得分:1)

其他人已经指出了这样一个事实,即扩展方法不会改变原始类型,所以只需添加一点:请记住,只有在导入定义命名空间后,扩展方法才可见。因此,如果您向字符串“添加”某些方法,则只有在您明确请求这些方法时它们才可用。换句话说,可以控制扩展方法的范围。也许并不完美,但至少当有人将它们添加到项目中时,它们不会出现在任何地方。