方法可以被声明为静态的,但是否应该这样做呢?

417

ReSharper喜欢指出每个ASP.NET页面中可以变成静态的多个函数。 如果我将它们变成静态的,这会对我有所帮助吗?我应该将它们变成静态并移动到实用程序类中吗?


22
难道 Resharper 实际上在呼喊“低内聚,低内聚”吗?现在是时候看看这个方法是否真的属于该类。 - P.K
2
可能是使用私有静态方法的优势的重复问题。 - drzaus
14个回答

294

在我看来,性能、命名空间污染等都是次要的。问问自己,这个方法是否在逻辑上操作类型的实例,还是与类型本身相关?如果是后者,请将其变为静态方法。只有当它与您无法控制的类型相关时,才将其移动到实用类中。

有时候可能会有一个逻辑上作用于实例的方法,但暂时不使用任何实例状态。例如,如果您正在构建文件系统并且已经有了目录的概念,但尚未实现它,您可以编写一个返回文件系统对象类型的属性,并且它始终只是“文件”-但它与实例逻辑相关,因此应该是实例方法。如果您想将方法设置为虚拟,则这也很重要-您特定的实现可能不需要状态,但派生类可能需要。(例如,询问集合是否为只读-您可能尚未实现该集合的只读形式,但它显然是集合本身的属性,而不是类型的属性。)


1
我认为一个好的代码检查工具应该有一个选项,可以将消息限制在非虚拟方法中,因为基类方法实际上很常见地什么也不做。覆盖方法通常会做一些事情,但并不总是如此。有时候,像空的iEnumerable这样的类很有用,它的方法本质上忽略了实例,但需要实例来选择正确的方法使用。 - supercat
3
有时候有些方法在逻辑上作用于一个实例,但却并没有使用到该实例的任何状态。例如,我喜欢你在这个例子中使用了“例如”。 - PaulBinder

273

静态方法与实例方法
C#语言规范中的静态成员和实例成员解释了它们之间的区别。通常情况下,静态方法可以在某些极端情况下提供非常小的性能优化(有关详细信息,请参见这个答案)。

FxCop或代码分析中的规则CA1822说明:

“标记成员为静态之后,编译器将向这些成员发出非虚拟调用站点,这将防止每次调用时检查确保当前对象指针为非空。对于性能敏感的代码,这可能会导致可衡量的性能提升。在某些情况下,无法访问当前对象实例表示一个正确性问题。”

工具类
除非符合设计要求,否则不应将它们移动到工具类中。如果静态方法与特定类型相关联,例如ToRadians(double degrees)方法与表示角度的类相关联,则该方法作为该类型的静态成员存在是有意义的(请注意,这是一个为了演示目的而编造的复杂示例)。


2
编译器将会对这些成员发出非虚拟调用站点。实际上,这是“编译器可能会发出...”。我记得C#编译器使用callvirt而不是call来解决一些潜在的错误问题。 - Jonathan Allen
27
我直接从 FxCop 1.36 中复制粘贴的。如果 FxCop 错了,那就算了。 - Jeff Yates
9
不确定我是否欣赏“bullshit”的说法,这是一种对待陌生人非常粗鲁的方式。但是,其中的观点是正确的;我已经稍微更新了一些东西(那是9年前的事情,所以我不记得我最初主张的基础是什么)。 - Jeff Yates
17
@Maxim 你的观点是无效的。我向你保证,9年前我的评分并不好。我欣赏指出错误(或更正它们的编辑)的评论,但请不要粗鲁,也不要对别人抱有不合理的期望。不要称某事为“胡说八道”;这意味着故意欺骗而不是诚实的错误或无知。这是粗鲁的行为。我自愿在这里帮忙,当它受到不尊重的对待时,真的感觉毫无意义。不要告诉我该受到什么冒犯-那是我的选择,而不是你的。学会以尊重和诚信的方式表达你的观点。谢谢。 - Jeff Yates
3
@Maxim 虽然委托不会与每个实例一起存储,但是无状态类的每个实例确实占用堆上的一些内存,这是无用的开销。通常在应用程序中实例化服务不是热路径,但如果您的应用程序最终构建了大量这些对象,则会产生GC压力,可以通过简单地使用静态方法来避免。 OP的原始声明,在极端情况下,静态方法比无状态实例提供性能优势,是合适的细微和有效的。 - Asad Saeeduddin
显示剩余7条评论

65

在类中将一个方法标记为static能够明确地表明它不使用任何实例成员,在浏览代码时这一点可能很有帮助。

除非这个方法旨在与另一个与其概念相关联的类共享,否则您不一定需要将其移动到另一个类中。


22

我相信在您的情况下不会出现这种情况,但是在我维护的一些代码中,我看到了一些“坏味道”,其中使用了大量的静态方法。

不幸的是,它们是假设特定应用程序状态的静态方法。 (为什么不呢?我们只有一个用户每个应用程序!为什么不让User类在静态变量中跟踪它?)它们实际上是访问全局变量的高级方式。 它们还包含静态构造函数(!),这几乎总是一个坏主意。(我知道有一些合理的例外情况)。

然而,当静态方法因素化出不实际依赖于对象实例状态的领域逻辑时,它们非常有用。 它们可以使您的代码更易读。

只要确保将它们放在正确的位置。 静态方法是否会强制性地操作其他对象的内部状态? 是否能够充分证明它们的行为属于其中一个类? 如果您没有正确地分离关注点,您可能会在以后遇到麻烦。


5
你的问题与静态字段/属性有关,而不是静态方法。 - Asad Saeeduddin

15

2
我认为这一点被低估了。工具真正告诉你的是该方法仅操作其他类的成员。如果它是某种命令(或“用例”或“交互器”)对象,其责任是操作其他对象,那就没问题。 但是,如果它只操作单个其他类,那听起来很像Feature Envy - Greg
1
答案中的第一个链接已失效。 - Pang

10

补充一下@Jason True的答案,请注意仅仅在方法上加上“static”并不能保证方法是“纯”的。关于声明方法的类,它将不具备状态,但它可能会访问其他有状态的“静态”对象(如应用程序配置等)。这可能并不总是坏事,但我个人倾向于在可以使用静态方法时使用它们的原因之一是,如果它们是纯的,就可以在不必担心周围状态的情况下测试和理解它们。


8
在类中处理复杂逻辑时,我发现使用私有静态方法可以创建隔离的逻辑块,其中实例输入在方法签名中明确定义,不会出现实例副作用。所有输出必须通过返回值或out/ref参数进行。将复杂逻辑分解为无副作用代码块可以提高代码的可读性和开发团队对其的信心。
另一方面,这可能会导致类被大量实用程序方法污染。通常情况下,逻辑命名、文档和团队编码规范的一致应用可以缓解这种情况。

6

在特定情况下,您应该做最易读和直观的事情。

性能论点除了在极端情况下外并不是一个好的论点,因为实际发生的唯一事情就是将一个额外的参数 (this) 推送到实例方法的堆栈上。


5

ReSharper并不会检查逻辑。它只会检查方法是否使用实例成员。 如果该方法是私有的,且仅被(可能只有一个)实例方法调用,则这表明应将其作为实例方法。


4

我希望你已经明白了静态方法和实例方法之间的区别。同时,这个问题有长答案和短答案。其他人已经提供了长答案。

我的短答案:是的,你可以像ReSharper建议的那样将它们转换为静态方法。这样做没有害处。相反,通过使该方法成为静态方法,您实际上正在保护该方法,以便您不会无意中将任何实例成员放入该方法中。通过这种方式,您可以实现面向对象编程原则“最小化类和成员的可访问性”。

当ReSharper建议将实例方法转换为静态方法时,它实际上是在告诉您:“为什么这个方法坐在这个类中,但实际上却没有使用它的任何状态?”因此,它给您提供了思考的食物。然后,你可以意识到将该方法移动到静态实用程序类中的需要与否。根据SOLID原则,一个类应该只有一个核心职责。因此,您可以通过这种方式更好地清理您的类。有时,即使在您的实例类中,您也需要一些辅助方法。如果是这样的情况,您可以将它们保留在一个#region helper中。


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