C# 静态变量和方法邪恶吗?Derek Comartin 解释(视频分解)
在 C# 软件开发的世界里,您很可能遇到过 static 关键字--无论是在静态 void Main、静态变量还是静态方法中。 但是,静态总是一个好主意吗? 或者,正如一些开发人员警告的那样,这些工具在大型应用程序中是否存在危险?
为了了解事实真相,我们将通过 CodeOpinion.com 的 Derek Comartin 提供的"Static Variables & Methods are Evil?"详细视频来了解静态成员可能成为问题的细微原因,但并非总是如此。 我们将使用他的例子和时间戳来指导这次深入研究。
是什么导致静态方法出现问题?
在视频的开头,Derek 介绍了一个名为 Is18YearsOrOlder 的静态方法。 此方法获取 DateTime birthDate 并检查某人是否年满 18 岁。它使用 DateTime.UtcNow 来比较当前日期。 很简单吧?
但正如德里克所指出的,这种方法是非确定性的。 在 0:50 处,他强调使用 DateTime.UtcNow 意味着该方法将根据运行时间返回不同的结果。 这是单元测试中的一个主要问题,会导致代码中出现意外行为。
在本例中,虽然该方法看起来像一个纯函数,但实际上并非如此。 Derek 解释说,纯方法每次使用相同参数调用时必须返回相同的值。 但在这里,当前日期不断变化,因此返回值也在不断变化。
这说明了公共静态方法虽然方便,但如果依赖于实时数据或应用领域状态,就会带来副作用。
使静态方法可测试、可预测
Derek 的下一点至关重要:我们可以通过移除对 DateTime.UtcNow 的依赖来解决非确定性问题。 Derek 没有介绍如何使用静态类或接口实现来注入时间提供程序,而是介绍了如何使用静态类或接口实现来注入时间提供程序。 这使得函数具有确定性--每次传递相同的输入都会得到相同的输出。
在他的 PlaceOrder 类中,他引入了一个假日期提供者,这样他就可以测试在周五处理的订单是否可以获得 50% 的折扣。 这样可以避免与系统时间挂钩的硬编码逻辑,使方法更加可靠和可测试。
通过隔离行为和避免在业务逻辑中直接引用静态方法,Derek 展示了如何在保持可测试性的同时保留简洁的代码。
紧密耦合和静态方法
Derek 在此提醒,依赖静态方法往往会带来紧密耦合。 如果您直接使用 DateTime.UtcNow,您将受该实现的约束 - 您不能覆盖或模拟它。
这是一个问题,因为像这样的静态成员在您的应用程序中是全局性的。 如果您的代码库大量使用静态字段或静态属性,就很难改变行为或注入依赖关系,从而破坏了面向对象编程的关键原则。
由于不能像实例变量或注入服务那样用不同的实现来替代静态字段,因此也就失去了灵活性。
静态变量的全局状态问题
现在,Derek 将重点转向静态变量,这也是对话变得严肃的地方。
他介绍了一个在 Global 类中使用静态缓存的例子。 他解释说,静态变量的最大问题是状态未知。 在运行时,您无法确定静态字段是否已被初始化。 当涉及到可变的静态 int 或字符串名称时,这种不可预测性尤其具有风险。
当开发人员假定跨线程共享的变量只有一个副本,而忘记考虑线程安全时,这种情况就会变得更糟。
多线程代码中的线程安全和静态字段
Derek 还提出了另一个问题:在多线程环境中使用静态变量。 他举例说明了静态 List
为了解决这个问题,他改用了 ConcurrentBag
他的观点很明确:如果要跨线程使用静态变量,请确保它们是线程安全的。 否则,您的程序可能会出现不可预知的行为,甚至崩溃。
静态方法的安全使用
然后,德里克分享了静态方法的安全有效用法:一个简单的实用方法 MilesToKilometers。 它接收 int 里数,转换后返回 double 值。 这种方法是确定性的--对于相同的 int 值,总是得到相同的结果。
这种方法不依赖非静态字段,不改变共享数据,也不涉及任何未知状态。 这是一个很好的例子,说明了如何在 C# 中正确使用静态关键字。
在 .NET 环境中理解静态语言
在 C# 中,static 关键字可用于类、字段、方法、构造函数和属性。 Derek 间接涉及了以下概念:
静态类:不能实例化且只能包含静态成员的类。
静态字段:使用静态关键字声明--每个应用领域只存在一个副本。
静态构造函数:仅在首次访问类时运行一次。
Static void Main:大多数 C# 应用程序的入口点,展示了静态方法的重要性。
- 静态 int、静态字符串:静态字段的示例,这些字段存储类的所有实例所共有的数据,或者实际上根本不需要实例。
与每次创建对象时都要运行的实例构造函数不同,静态构造函数只初始化一次类级资源。
这种区别有助于开发人员决定何时使用实例变量与静态变量,或何时使用属性访问器封装共享成员变量。
Derek 的最终收获
Derek 总结了开发人员对静态成员持谨慎态度的主要原因:
紧密耦合--您将无法摆脱静态方法或字段的行为。
非确定性行为--难测试,易破坏。
全局可变状态--您不知道值是什么,也不知道是谁改变了它。
- 并发问题--多线程代码中对共享数据的不安全访问。
然而,正如 Derek 所说,静态并不邪恶。 如果使用得当,特别是在实用功能、共享常量或真正的全局设置中,它的功能会非常强大。 您只需小心管理状态,避免依赖于易变或系统特定的行为。
结论
静态变量和方法在 C# 中是一把双刃剑。 正如 Derek Comartin 清楚解释的那样,这些工具本质上并不坏--但在使用时需要深思熟虑。 当您需要共享数据或不依赖于对象状态的功能时,请使用静态字段和静态类。 但要避免将它们用于依赖时间、系统状态或需要灵活性的事情上。
因此,在创建对象或访问静态字段之前,请考虑范围、可测试性、线程安全性,以及代码是需要一个副本还是多个实例。
在他的 CodeOpinion YouTube 频道上观看 Derek Martin 的完整视频。 您会发现更多关于简洁架构、软件设计和真实 C# 应用的见解。

