我最近遇到了一个我在使用 C# 时遇到的问题,它通过使用反射设置一个私有成员来解决。
我惊讶地发现设置私有成员/字段并运行私有方法是 C# 中允许和可能的事情。这不是如何做这些事情的问题,它们有据可查,我的问题是:为什么?
如果您将字段/成员/方法设置为私有/内部,为什么 C# 作为一种语言允许在范围之外设置这些字段?我认为这会引发某种异常。如果类希望它们被更改或设置,不会有方法或构造函数吗?
我最近遇到了一个我在使用 C# 时遇到的问题,它通过使用反射设置一个私有成员来解决。
我惊讶地发现设置私有成员/字段并运行私有方法是 C# 中允许和可能的事情。这不是如何做这些事情的问题,它们有据可查,我的问题是:为什么?
如果您将字段/成员/方法设置为私有/内部,为什么 C# 作为一种语言允许在范围之外设置这些字段?我认为这会引发某种异常。如果类希望它们被更改或设置,不会有方法或构造函数吗?
因为访问修饰符可以帮助记录您想要向消费者或继承者等公开的 API。
它们不是安全/访问控制机制。
阻止某人能够做到这一点是不可能的。你可以让它变得更难,你可以强迫他们使用不安全的代码并开始盲目地设置位。毕竟,这是他们的程序/机器,他们被允许做这样的事情。
该语言的设计使您很难自欺欺人并做坏事。但这并不会使它们成为不可能,这样做也会限制用户做一些不寻常但仍可取的事情。
私人反思要求您获得基本上完全的信任。完全信任意味着完全信任。如果您完全相信自己会做正确的事,那么为什么不这样做呢?
(事实上,私有反射的安全模型实际上比我在这里勾勒的要复杂得多,但这并不影响我的观点:这种能力受策略的约束。有关概述,请参阅反射的安全注意事项(MSDN)反射和安全策略如何相互作用。)
可见性修饰符并不意味着安全。
它们只是为了使结构化 API/代码库的工作更有效——以免程序员因胡言乱语而陷入困境——并且只公开消费者应该关心的事情。
反射是您与已编译代码交互的方式。如果反射尊重源语言对隐私的期望,那么就需要另一种机制,就像它没有的那样。乌龟一路下来。
首先假设它所做的是它的本意,然后重新评估你的假设,即做一些没有人想做的不同的事情是一种错误的努力。
有时,你必须作弊。
很容易说,如果需要设置,类应该有一个设置器,但如果你不能添加一个怎么办?如果它不是您的类,而是一个专有库,您需要解决其中的错误怎么办?我并不是说你应该做这些事情,事实上我会说你几乎肯定不应该这样做,但有时让某些事情发挥作用的唯一方法是无耻的作弊。在这些情况下,这些机制的存在是非常有帮助的。
但是,您应该始终质疑它们的使用,当然要注意,违反对象的公共 API 可能会导致您进一步出现问题,并且可能是错误的做法。除非在上述情况下,这是您可以在某些您无法更改的代码中使某些东西工作的唯一方法。
需要注意的一点是,C++ 及其衍生版本具有强大的访问控制能力,但也有很多 OOP 语言没有。例如,Perl 5 对象对外部干扰完全开放,私有方法和数据只是按照惯例存在 - Perl 程序员知道在第三方对象的 hashref 内部搞乱可能会破坏事情,所以他们通常会赢不要那样做。再次与 C# 的反射功能一样,有时您可以通过对您不应该触及的东西进行一点调整来解决大量问题。
但我再次重申,你几乎肯定不应该这样做。这些功能不适用于日常使用。
如果没有反射性的东西,我什至看不到我的脸。好吧,我可以在视频中观看自己,但这是由称为相机的光学设备完成的,它也是使用反射技术的东西。
如果我生病了,当医生需要X光来诊断和治疗我的病时,我可能已经死了,但各种反思都无法看到我的隐私。我也永远看不到自己。
X 射线(或类似断层扫描的东西)可以观察我的内部,没有它我永远做不到。
我宁愿说它比反思更现实。但是,是的,我不能用我的眼睛直接看到真实的我,我所见过的每一个我,都是某种反映。(深入思考,眼睛也给了我们现实的反映。)
因此,反思应该与现实有关,没有特定的观点。并且您可以假设在面向对象的规则内,消费者代码仅限于 BindingFlags.Public。
在真实的宇宙中,几乎没有什么是不可能的;对我们来说,可能和不可能之间的唯一区别是它是否可以由人类完成,因为我们是人类。
反射可以完成程序世界中的所有事情似乎很危险,现在出于安全原因,在人类逻辑中需要完全信任它。
好问题。
我的回答是:访问修饰符和可见性是 API 设计的一部分。通过反射,您可以完全控制几乎所有内容,包括绕过 API 和在低级别修改数据。
访问修饰符可以使您的类免受外部意外更改的影响。您可以“偶然”调用方法或访问公共成员。通过反思,很难坚持认为您是偶然做的事情。
如果这种低级别的反射访问是不可能的,那么我们都知道和喜欢的很多东西几乎是不可能的。
生产代码中的一个案例:
我们希望一个 Web 服务可以在 NZ 时区工作。可能正确的解决方案是重写我们所有的代码(包括一些 .NET Framework 序列化代码)来使用DateTimeOffset
,但最简单的解决方案是有效地调整 .NET Framework 中存储当前时区的两个私有字段(通常基于注册表调用) 明确使用新西兰时区。
我们知道,如果 .NET Framework 2.0 版在处理时区方面有所更新,我们可能不得不重新编写代码,但目前这在生产环境中运行良好。