C# 修饰符的使用与滥用 – 第一部分






2.78/5 (3投票s)
C# 修饰符 - 使用与滥用
距离我上一篇博文已经有一段时间了,我想通过写一些我在现实生活中注意到的关于 C# 修饰符的现象以及我对它们的看法来弥补一下。
我们要讨论哪些修饰符?嗯,修饰符的列表可以在 MSDN 上找到,我们将尝试遵循并结合更多内容,不仅是修饰符的作用,还有它们在实际应用中如何被遇到、使用,或者滥用(是的,在这种情况下,根本不使用修饰符也算滥用)。
一个小免责声明,我非常喜欢我称之为“限制性编程”(不知道这个术语是否有人之前用过)。我的意思是,我更倾向于实现最严格的设计(通过使用 private、abstract、constant、read-only,我们很快就会看到它们),这样我和其他人现在以及将来都会被迫考虑“为什么?”。为什么我们需要某样东西是 public 或 mutable,然后再做?是否存在我们忽略的、或者有其他通常更简洁的方法?
首先,也是我经验中的一大痛点是访问修饰符。在大多数课程和书籍的开头,你会首先接触到它们,而且它们似乎是最被忽视的。我们将按照 MSDN 页面上列出的顺序反向查看它们,并从我的角度来看,它们应该如何应用。
Private
它仅适用于类成员,包括属性、字段、方法、事件和嵌套类型,并且只能由包含类访问。遗憾的是,我在代码库中发现这个修饰符被低估了。我认为,所有类应该从将所有成员默认设置为 private 开始,除非有证据表明需要更高访问权限。这就是为什么 C# 将成员的访问修饰符缺失解释为隐式 private
,同样,对于那些还记得 C++ 的人,private
也是默认的访问权限。
遵循此方法的优势
- 你(理想情况下)需要在将成员提升到更高访问权限之前提供理由。
- 所做的任何更改都将保留在本地,因此任何依赖类或工作流程都不需要更改。
- 易于利用你的 IDE(在本例中是 Visual Studio + Resharper)来查找需要提升或不提升的内容,从编译错误中获取信息,编译错误就像异常一样是我们的朋友。
不遵循此方法的后果
- 如果它是可访问的,Intellisense 就会找到它。这意味着其他团队成员、客户甚至未来的你可能会陷入使用本应仅为 private 的类成员的陷阱,稍后才意识到更改它将需要更改整个代码库。这还会导致查找解决问题的方法、引入新 bug 或只是让代码腐烂的时间成本。
我遇到的另一个想分享的场景是这样的,我们都知道不应该给方法传递太多参数(最多 3 个),因为很难保持上下文和跟踪它们(对吧?),所以我们大多数人(是的,我也曾陷入过这个陷阱)会创建一个类来保存参数,就是那个传说中的“….Options
”类。请记住,这适用于 public
或 internal
面向的 API,但对于 private
方法,如果它仅在当前类的上下文中用于,那么一个 private
嵌套类实际上更简洁。
另外一点我认为对 C# 影响尤为严重的是属性。我也使用它们,但是……当我们做 C++ 时,我们不得不手动定义 getter 和 setter,我们曾经会更仔细地考虑实现。现在由于属性的出现,它们有时被滥用,成为类或甚至接口上 public
字段的替代品。别误会我的意思,POCO 类很棒,但仅限于 POCO 类,没有方法,也没有基于类状态的逻辑。此外,如果所需的参数很小且使用稀疏,请考虑使用 struct
而不是类。
内部
这是所有在 namespace
级别定义的 class
和 struct
的默认访问权限,它基本上与 public
声明相同,但它们只能从项目或程序集内部访问。我认为所有类都应该从那里开始,而不是 public
,除非有证据表明需要。在我遇到的大多数代码库中,大多数类都被声明为 public
。
遵循此方法的优势
- 默认情况下拥有
internal
类可以确保你作为开发人员设定的工作流程得到尊重。 - 声明为
internal
的类也可以像public
类一样受益于单元测试,方法是在程序集级别添加一个属性,该属性声明哪些其他项目可以访问这些类(请参阅 InternalsVisibleTo 属性)。 - 这是一个好习惯,即使是微软在他们自己的内部实现中也使用。如果你从未反编译过 .NET Framework 程序集或查看过 .NET Framework 的在线源代码,请务必这样做,你将看到一层又一层的内部类。
- 你可以更改程序集的实现,而不会破坏
public
API,因为它们从不以此方式暴露。
不遵循此方法的后果
- 默认情况下不将实现设为
private
会让你暴露于创建可能被以非预期方式使用的 API。这里有一个例子,在我工作的一个项目中,有一个数据访问程序集,其中所有内容都是public
的,这导致大量项目到处创建临时连接,因为没有限制来强制你作为开发人员遵循结构化的工作流程。
这里有一个我在开发个人项目时发现的技巧,你可以拥有一个面向公众的 API,使用工厂类(我们将在第 2 部分讨论 abstract
类时了解更多),以及接口。因此,本质上,你可以向用户展示一个 abstract
类,它不能被实例化,但有一个工厂方法,该方法返回该基类的派生类的内部实现。这让你能够控制对象的实例化方式,如果你想公开一些经过严格测试的类,它们可能需要多个步骤才能实例化,那么你可以根据自己的步骤强制它们的实例化。如果有人想从这些基类派生或实现这些 public
接口,那是他们的自由。
Protected
通过应用此修饰符,我们强制要求成员或类只能在层次结构链中使用。
作为个人观点,这是我在处理层次结构链时首先考虑的步骤,甚至在考虑公共方法之前。同样,在考虑 public virtual
成员(对于方法和属性)之前,请考虑应用此限制。
Protected Internal
这是两全其美,可惜我在实际应用中很少看到。应用此修饰符时,成员/类只能在层次结构链(例如 abstract
类)或内部使用。
Public
这个修饰符不幸的是最常遇到,也是导致长期开发问题和头痛最多的。拥有 public
类和成员本身并没有什么错,它们甚至很有必要,特别是对于库和框架开发,但请仔细考虑为什么它需要是 public
。与其拥有大量面向公众的 API,不如考虑是否可以将一些功能封装起来并经过严格测试,然后再默认设为 public
。
结论
请记住 OOP 编程的四大支柱:抽象、封装、继承和多态。在此过程中,你会遇到那些持有“白盒”心态的人,尤其是在内部项目中,但也请考虑,即使你知道微波炉的工作原理并且可以拆开它,你也不想逐个焊接零件然后欢呼雀跃地因为它有效,却发现你还把桌子也焊在了零件上。
限制性方法从长远来看提供了更多的好处,这里重新列举其中一些:
- 更新和修改更容易,尤其是在你自己的项目中。如果是一个公共框架,那么支持本就不应成为功能的东西的向后兼容性,那就祈祷你好运吧。
- 将逻辑抽象到自己的项目中并隐藏实现,会让你重新考虑直接调用数据库或服务,而不是通过正确的流程(想象一下直接从业务层或 UI 调用 Entity Framework,而不是通过存储库抽象,当您从 Entity Framework 切换到其他东西时会发生什么?存储库的目的是什么?)。
- 继承更容易,因为你不必担心基类的专门派生是否被妥善打包并且只能通过适当的机制访问。
- 它会让你在全力以赴更改某项内容并将其设为 public 之前(希望如此!!!)始终质疑一个决定。
我知道这是一篇很长的帖子,而且到最后有点失去了动力,但我希望尽快把最重要的部分提出来。
谢谢,在第 2 部分,我们将看看一些其他修饰符,它们的使用频率远远不够。请记住,仅仅因为它可以编译并工作,并不意味着你想把整个桌子都带着你新焊接的微波炉一起搬走。
祝好,下次见(我保证很快 )。