何时创建扩展方法是正确的?

14

我有一段像下面这样的代码:

public class ActivityHelper
{
    public void SetDate(IList<Activity> anActivityList)
    {
        foreach(Activity current in anActivityList)
        {
            current.Date = DateTime.Now;
        }
    }
    //More methods, properties, fields, etc...
}
这可以很容易地转换为扩展方法。例如:
public static void SetDate(this IList<Activity> aList)
{
    foreach(Activity current in anActivityList)
    {
        current.Date = DateTime.Now;
    }
}

原始函数并未使用 ActivityHelper 类中的任何实例特定数据或方法,这使得它似乎放错了位置。现在写扩展方法是否正确?创建扩展方法的正确情况是什么?


1
看起来你问错了问题。无论你将它变成扩展方法与否都不是很重要。但你几乎肯定应该将其变成一个静态方法。你的第一个方法虽然没有使用任何实例成员,但却是非静态的;这是一个(轻微的)设计失误。 - Timwi
@Timwi 是的,我完全可以将该方法设为静态的,而且在这个例子中没有特定的原因我没有这样做。但无论哪种情况,我仍然觉得这是一种笨拙的方法。 - brainimus
我发现扩展方法最好用于目标程序集或类是“关闭更新”的情况。我还发现人们滥用扩展方法来规避单一职责原则(SRP)。在我看来,静态类和扩展方法是最容易被误用的功能。 - Prisoner ZERO
不是你要求的内容,但由于循环需要一些时间运行,有时候一些活动会比其他活动获得稍微不同的“日期”。如果这是不希望的,请在循环之前读取一次“DateTime.Now”,并在循环内使用该值,以便所有项目都获得相同的“DateTime”。 - Jeppe Stig Nielsen
5个回答

12

Brad Adams写过一篇关于扩展方法设计指南的文章:

如果满足以下任何情况,请考虑使用扩展方法:

  • 为每个接口的实现提供有用的帮助功能,如果该功能可以根据核心接口编写。这是因为具体实现无法分配给接口。例如,LINQ to Objects运算符被实现为所有IEnumerable类型的扩展方法。因此,任何IEnumerable<>实现都自动启用了LINQ。

  • 当实例方法会引入对某个类型的依赖关系时,但这种依赖关系会违反依赖管理规则。例如,从String到System.Uri的依赖关系可能不可取,因此返回System.Uri的String.ToUri()实例方法在依赖管理方面是错误的设计。一个静态扩展方法Uri.ToUri(this string str)返回System.Uri将是更好的设计。


破损的Brad Adams链接已移至- https://progressivewebexperience.io/blog/brad_abrams/2009/01/framework_design_guidelines_extension_methods - undefined

6
我认为,只有在有充分理由将方法设置为扩展方法时,扩展方法才是合适的选择。
如果类型不受您控制,而且该方法应该看作是该类型的组成部分,或者如果有充分理由不直接将方法放在类型上(例如创建不需要的依赖项),那么扩展方法可能是合适的选择。
个人而言,如果用户使用 API 时已经预期要使用“ActivityHelper”类来处理活动集合,则我可能不会为此创建扩展方法。标准的非扩展方法实际上将是更简单的 API,因为它易于理解和发现。从使用的角度来看,扩展方法比较棘手 - 您调用的方法“看起来”存在于其他地方,而不是实际存在的地方。虽然这可以简化语法,但它会降低可维护性和可发现性。

2
在发现性方面是真实的。如果您不知道扩展方法的存在,您将永远找不到它 - 除非您已经包含了正确的命名空间,否则它不会出现在智能感知中。 - Kirk Broadhurst
1
@Kirk:是的,即使你知道它“存在于某个地方”,找到文档等也可能很棘手,除非你知道它来自哪里... - Reed Copsey

4

根据我的经验,扩展方法在以下情况下效果最佳:

  • 没有副作用(我们团队编写的大多数具有副作用的扩展方法最终都被删除了,因为它们带来的问题比它们帮助的要多)
  • 提供适用于类型的每个可能实例或值的功能。 (再次引用我团队的一个示例,string.NormalizeUrl()不适合,因为并非所有字符串都是URL)

不确定第一个,Linq 有很多副作用。我同意开发人员可能倾向于组合语句并省略安全编码实践,但显然需要注意副作用。 - Yannick Motton
这并没有真正回答中心问题,即在“Helper”类中是否应该使用扩展方法还是常规方法。 - Cody Gray
OP提出的第二个问题是更一般性质的。我想这就是Rex所回复的,而我只是做了一个评论。 - Yannick Motton
@Yannick,LINQ中的扩展方法是专门设计为具有副作用的。 - Rex M
这可能适用于延迟执行的 Linq 方法,但消耗枚举的方法甚至可能访问数据库。我们正在谈论同样类型的副作用吗? - Yannick Motton
显示剩余2条评论

0

0

本质上,扩展方法为辅助方法提供了更流畅的语法风格。这意味着可以似乎地向类型或所有接口实现添加功能。

然而,我通常避免声明返回类型为void的扩展方法,因为我觉得当所涉及的方法不返回任何内容时,这种流畅的语法风格允许您组合语句的有用性被抵消了。

然而,我猜想让您的方法被 IntelliSense 捕捉到可能会很方便... :-)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接