好吧,假设我们有一个类定义为
的类public class TestClass
{
private string MyPrivateProperty { get; set; }
// This is for testing purposes
public string GetMyProperty()
{
return MyPrivateProperty;
}
}
然后我们尝试:
TestClass t = new TestClass { MyPrivateProperty = "test" };
正如预期的那样,编译失败了TestClass.MyPrivateProperty is inaccessible due to its protection level
。
尝试
TestClass t = new TestClass();
t.MyPrivateProperty = "test";
并且编译再次失败,并显示相同的消息。
直到现在一直很好,我们期待着这一点。
然后一个人写道:
PropertyInfo aProp = t.GetType().GetProperty(
"MyPrivateProperty",
BindingFlags.NonPublic | BindingFlags.Instance);
// This works:
aProp.SetValue(t, "test", null);
// Check
Console.WriteLine(t.GetMyProperty());
我们在这里,我们设法改变私人领域。
仅使用反射能够改变某个物体的内部状态是不是不正常?
修改
感谢目前为止的回复。对于那些说“你不必使用它”的人:对于一个班级设计师来说,看起来他不能再承担内部国家安全了吗?
答案 0 :(得分:13)
反射通过提供对私有字段和方法的访问来打破封装原则,但它不是第一种或唯一可以绕过封装的方式;有人可能会争辩说序列化会暴露一个类的所有内部数据,这些信息通常是私有的。
重要的是要理解封装只是一种技术,如果消费者同意使用您定义的API,则可以使设计行为更容易。如果有人选择使用反射或任何其他技术绕过您的API,他们就不再能保证您的对象将按照您的设计行事。如果有人将null
的值分配给私人字段,那么下次他们尝试使用您的课程时,他们最好准备好抓住NullReferenceException
!
根据我的经验,编程完全是关于断言和假设的。语言断言约束(类,接口,枚举),这使得创建隔离行为更容易生成,假设消费者同意不违反这些边界。
这是一个公平的断言,因为它比以往任何技术都更容易实现软件开发的分而治之。
答案 1 :(得分:8)
反思是一种工具。你可以使用它来打破封装,当它给你带来的东西超过了它。
反射具有与之相关的某种“痛苦”(或成本 - 性能,可读性,代码可靠性),因此您不会将其用于常见问题。对于常见问题,遵循面向对象的原则只是更容易,这是该语言的目标之一,通常称为the pit of success。
另一方面,如果没有这种机制,有些任务是无法解决的,例如:使用运行时生成类型(但是,使用它的DLR和C#4.0中的“动态”变量从.NET 4.0开始会更加容易。)
答案 2 :(得分:3)
我想你可以这么说。您还可以说CodeDom,Reflection Emit和Expression Tree API允许您动态编写代码来破坏封装。它们仅仅是.NET提供的工具。你不必使用它们。
不要忘记您必须(a)以完全信任的方式运行,并且(b)明确指定BindingFlags.NonPublic
,以便访问私有数据。这应该足够安全网。
答案 3 :(得分:3)
你是对的,反思可以与任何数量的优秀设计原则相对立,但它也可以成为你可以用来支持良好设计原则的重要组成部分 - 例如可以通过插件,控制反转等扩展的软件
如果你担心它代表了一种不应该劝阻的能力,你可能会有一点意见。但它不像真正的语言功能那样方便,因此以正确的方式做事更容易。
如果你认为反思应该不可能,你就是在做梦!
在C ++中没有这样的反思。但是有一个底层的“对象模型”,编译器生成的机器代码用它来访问对象(和虚函数)的结构。因此,C ++程序员可以以相同的方式打破封装。
class RealClass
{
private:
int m_secret;
};
class FakeClass
{
public:
int m_notSecret;
};
我们可以指向RealClass
类型的对象,只需将其转换为FakeClass
并访问“私有”成员。
任何受限制的系统都必须在更灵活的系统之上实现,因此始终可以绕过它。如果没有提供反射作为BCL功能,有人可以使用不安全的代码将其添加到库中。
在某些语言中,有一些方法可以封装数据,因此除了某些规定的方式外,语言中的不可能获取数据。但是,如果你能找到摆脱语言的方法,那么总是可以作弊。一个极端的例子是JavaScript中的范围变量:
(function() {
var x = 5;
myGetter = function() { return x; };
mySetter = function(v) { x = v; };
})();
执行之后,全局命名空间包含两个函数myGetter
和mySetter
,这是访问x
值的唯一方法。 Javascript无法以任何其他方式获取x
。但它必须在某种主机解释器中运行(例如在浏览器中),因此操纵x
肯定有一些可怕的方法。插件中的内存损坏错误可能会偶然发生!
答案 4 :(得分:2)
可以说反射也会破坏继承和多态性。当不是为每个对象类型提供自己的重载方法版本时,您有一个方法可以在运行时检查对象类型并切换到每个对象类型的特定行为。
然而,反射是一个很好的工具。有时您需要使用私有构造函数实例化一个对象,因为它是一个最佳的操作过程,您无法更改类实现(封闭库,无法再与作者联系)。或者您希望在一个地方对各种物体执行一些辅助操作。或者您可能希望对某些类型执行日志记录。反射确实有助于不造成任何伤害。
答案 5 :(得分:2)
这是Reflection提供的功能的一部分,但不是,我会说,最好用它。
它只能在完全信任下运作。
答案 6 :(得分:2)
不,这不是异常 反射允许你这样做,在某些情况下你所做的可能会有所帮助,在许多其他情况下只会使你的代码成为定时炸弹。
反射是一种工具,可以使用或滥用。
关于编辑后的问题:答案是否定的。
API设计人员可以尽力而为,通过大量的工作来暴露一个干净的界面,并通过反射看到他的API过度滥用。
工程师可以建造一个安全的完美洗衣机,不会给你带来电击等等,但是如果你用锤子砸它,直到你看到电缆没有人会责怪工程师如果你得到的话震惊:D
答案 7 :(得分:2)
封装原则由您的API保留,但反射为您提供了一种解决API的方法。
使用反射的人都知道他没有正确使用您的API!
答案 8 :(得分:1)
反射可以帮助您保持代码清洁。例如。当你使用hibernate时,你可以直接将私有变量绑定到DB值,而不必编写你不需要的不必要的setter方法。从这个角度来看,反思可以帮助你保持封装。