为什么可以使用反射在C#中更改私有成员或运行私有方法?

88

我最近遇到了一个使用C#时的问题,通过使用反射来设置私有成员后,问题得以解决。

我对于在C#中设置私有成员、运行私有方法这些操作是允许且可行的感到惊讶。这不是关于如何执行这些操作的问题,它们已经有很好的文档说明。我的问题是:为什么?

如果将字段/成员/方法设置为私有或者友元,为什么C#编程语言允许外部对这些字段进行设置?我认为这应该会引发某种异常。如果类希望更改或设置它们,难道不应该提供一种方法或构造函数吗?


95
如果你使用反射、手动发射字节码等技术,就意味着放弃了常规的面向对象模型,表示“我需要现在就这样做”。这意味着常规的限制被解除,因为你明确表达了绕过它们的意图。伴随着强大的能力而来的是巨大的责任。 - Patashu
9
请参考:反射的安全性考虑 - Ani
13
只是为了严谨,C# 编程语言本身并不允许这样做。System.Reflection 命名空间中的类不是专属于 C# 的。 - Chris Dunaway
37
如果调试器无法访问你正在调试的类的私有成员,它们将不会非常有用。 - Raymond Chen
5
附带一提,考虑一下这段小代码的乐趣:typeof(String).GetField("Empty", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.GetField).SetValue(null, "foo" ); - Quibblesome
显示剩余16条评论
9个回答

132
因为访问修饰符主要用于辅助记录您想要向消费者或继承者等显示的 API,并非安全/访问控制机制。

3
我认为存在关键字unsafe意味着即使没有反射,你也可以访问C#数据类型的私有字段。(我不敢尝试) - Patashu
8
即使这个功能不存在,你仍然可以调用C++代码来在进程的任意内存位置随意地扭曲位。最终,你无法阻止程序操纵自己的内存空间;为了能够正常运行,它需要足够的权限来几乎任意地处理该内存。 - Servy
6
我认为完全否定访问修饰符是一种安全访问控制机制并不公平,尽管它们无法提供OP所讨论的那种安全性。请参见访问级别和修饰符(private,sealed等)在C#中是否具有安全目的?。根据Eric Lippert的说法:“访问限制通过对部分受信任的敌对代码进行限制,减轻了针对用户的攻击。” - Brian

63

防止某人这样做是不可能的。您可以增加难度,迫使他们使用不安全的代码并开始盲目地设置位。 毕竟,这是他们的程序/机器,他们允许这样做。

语言的设计使您很难自毁而犯错。 但这并不是不可能的,这样做也会限制用户进行一些不寻常但仍然可取的事情。


11
这是我最喜欢的答案。修改值仅是访问内存的机制,因此有了庞大的“病毒预防/清除”市场。这个想法是,如果你让某些东西对消费者不太可见,他们就会意识到通常应该如何使用接口。这也意味着通过Reflection修改数据可能会导致不稳定性(尽管我已经做过):D - Mike Perrenoud
2
这并非不可能,只是真的很难。例如,您可以运行一个程序来拦截每个内存访问(使用 mmu),并通过单独的进程(使用调试 API)或通过内核或某种虚拟机监视器进行验证。这些监视程序可以内省内存访问并做出非常复杂的策略决策。虽然速度非常慢,但仍然是有可能实现的。 - Scott Wisniewski
@ScottWisniewski 不过,如果有人还篡改了实施访问策略的软件的内存,以便获得修改其他进程的访问权限呢? :p - grinch

56

私有反射需要您被授予基本完全的信任。完全信任意味着完全信任。如果您被完全信任能够做正确的事情,那么为什么不应该起作用呢?

(实际上,私有反射的安全模型比我在这里勾画的要复杂得多,但这并不影响我的观点:这种能力受制于策略。请参阅 Reflection 的安全注意事项 (MSDN) 了解反射和安全策略如何相互作用)


1
现在这是我所见过的关于私有反射最清晰的说明。它需要完全信任,谢谢Eric! - Mike Perrenoud
@Michael,实际上,在实践中这要复杂得多。我在简化以明确指出重点;此功能由策略控制。 - Eric Lippert
1
你评论中提到的“由策略控制”这个小细节也非常重要,这让我感到很有道理。虽然我还没有完全理解它,但它让我知道网络管理员可能也能够控制该策略,因此我不应该假设我总是可以访问私有反射。再次感谢你的建议,我需要进行一些阅读,但这是一个很好的提示。 - Mike Perrenoud

14

可见性修饰符并不是为了安全而存在。

它们的存在只是为了使得与结构化API/代码库一起工作更加高效 - 不会让程序员被琐碎的东西拖累 - 并且只暴露消费者需要关注的内容。


6

反射是您与已编译代码交互的方式。如果反射遵守源语言对隐私的期望,那么就需要另一种机制,该机制不遵循这些期望。一切皆为龟。

首先假设它所做的就是它应该做的,然后重新评估您的假设,即这是一个误导性的努力,试图做一些任何人都不想做的事情。


5
有时候,你必须作弊。
如果需要设置某个属性但无法添加setter怎么办? 如果这不是你的类而是专有库,并且你需要解决其中的一个错误呢? 我并不是说你应该这样做,实际上我会说你几乎肯定不应该这样做,但有时使事情正常工作的唯一方法就是犯规。 在这些情况下,这些机制存在的事实非常有帮助。
然而,你应该始终质疑它们的使用,并且当违反对象的公共API时可能会在后面引起问题,因此这可能是错误的事情。 除非在前面提到的场景中,这是您可以在不具备更改代码能力的某些代码中使其正常工作的唯一方法。
需要注意的是,C++及其衍生产品具有强大的访问控制功能,但有许多OOP语言不具备。例如,Perl 5对象完全开放给外部干扰,私有方法和数据仅由约定添加 - Perl程序员知道在第三方对象的哈希中搞砸可能会破坏事物,因此他们通常不会这样做。同样,如同C#的反射功能一样,有时候只需轻微调整就可以解决很多问题,尽管你不应该触碰它们。
但是,我再次重申,你几乎肯定不应该这样做。 这些功能不适用于日常使用。

5

我甚至没有办法透过反光物体看到我的脸。好吧,我可以在视频中看到自己,但那是通过被称为相机的光学设备完成的,它也使用了反射技术。

如果我生病了,医生需要用X光来诊断和治疗我的疾病时,所有种类的反射都无法看到我私人的部分。而且我也永远无法看到自己。

X光(或类似于层析成像的)可以观察我的内部,而我没有它就无法做到这一点。

1kUml.jpg

我更愿意说它比反射更现实。但是是的,我不能用眼睛直接看到真正的我,我曾经看到的每一个我,都是某种反射形式。(深入思考,眼睛也给我们反映现实的反射。)

因此,反射应该与现实有关,没有特定的视角。您可以假设消费者代码受到BindingFlags.Public的限制,符合面向对象编程的规则。

在真实的宇宙中,几乎没有什么是不可能的;对于我们来说,可能和不可能之间唯一的区别是是否可以被人类完成,因为我们是人类。

看起来反射可以在程序的宇宙中做任何事情,现在出于安全原因需要完全信任它,在人类的逻辑中这是危险的。


3

很好的问题。

我的答案是:访问修饰符和可见性是API设计的一部分。通过反射,您可以完全控制几乎所有内容,包括绕过API并在低级别上修改数据。

访问修饰符可以使您的类免受外部意外更改的影响。您可能会“意外地”调用方法或访问公共成员。通过反射,很难维护您是出于偶然而这样做的。

如果反射中的低级别访问不可能,我们所熟悉和喜欢的许多东西将几乎不可能实现。


0

生产代码中的一个案例:

我们希望一个 Web 服务在新西兰时区工作。可能正确的解决方案是重写所有代码(包括一些 .NET Framework 序列化代码)以使用 DateTimeOffset,但最简单的解决方案是有效地调整 .NET Framework 中存储当前时区的两个私有字段(通常基于注册表调用)以明确使用 NZ 时区。

我们知道,如果 .NET Framework 版本 2.0 在处理时区方面进行更新,我们可能需要重新设计我们的代码,但目前这在生产中运行良好。


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