为什么私有成员变量可以被类实例更改?

27
class TestClass
{
    private string _privateString = "hello";
    void ChangeData()
    {
        TestClass otherTestClass = new TestClass();
        otherTestClass._privateString = "world";
    }
}
这段代码可以在C#中编译,等效代码也可以在PHP中工作,但是有人能解释一下为什么这里可以更改otherTestClass._privateString吗?我原以为类的实例在任何情况下都不能更改私有成员变量,并且尝试访问otherTestClass._privateString会出现“无法访问由于保护级别”的错误。但事实并非如此,那么为什么在其自己的类内部实例化一个对象会让您访问私有成员呢?这样做是否应该,这不会在一定程度上破坏封装性吗?或者我错过了一些明显的东西吗?
(我不是在询问上述类设计是否是良好的实践,只是想知道其中的理论。)
编辑 - 感谢答案和评论。为了澄清,我也想知道能否将其视为积极的特征,或者它是否是更好的编译时检查/代码清晰度/因为大多数其他语言都是这样做的必要权衡。对我来说,理想情况下,编译器将防止或警告您进行此操作,但我远非语言设计师。任何关于它以这种方式让您执行某些有用操作(而不违反封装)的示例都将是极好的。

顺便提一句:你可以把 ChangeData() 改成 static 静态函数,这样你仍然可以访问私有成员。 - ulrichb
@ulrichb - 需要注意的是,如果ChangeData()是静态的,那么您不能使用this关键字来访问私有成员。 - TheCloudlessSky
1
作为一种不同的语言例子 - 在Ruby中,public是相同的,但private意味着“仅由同一实例可访问”,而protected意味着“仅从同一类可访问”。继承与此无关。 - Tesserex
2个回答

35

私有成员可被该类程序文本中的任何代码(包括嵌套类型)访问,与您正在处理哪个类的实例无关。

我认为这并不违反封装原则 - API 仍然与实现分离,但是实现“知道”自己无论查看哪个实例。

我认为在其他一些语言中,这种可访问性不是这样工作的,但对于 C# 和 Java 肯定是这样的。(Java 对于能够访问私有成员有稍微不同的规则,但翻译您所写的代码仍将起作用。)


3
在实例级别上实现可访问性需要针对每个成员变量访问进行运行时检查,而这种模型可以在编译时进行检查。 - James Gaunt
2
@James:嗯,并不一定。你可以要求私有成员只能通过“this”引用来访问。这可能很丑陋,但是可行的。 - Jon Skeet
1
我想补充一下Jon的回答,这种访问控制允许编写n元运算符,例如相等。如果没有任何实例可以访问其他实例的私有成员,则实现相等运算符将更加困难。 - Andy Lowry
我同意Andy的观点,但仍然认为类级别的隐私相比对象级别的隐私存在一些弱点。这就是为什么在Java中实现equals(Object other)时,我使用getter获取“other”对象的属性的原因。显然,这并不总是可能的 :( - AlexR
@Jon,感谢您的回答。有没有可能在问题的编辑部分添加一条评论呢? - Michael Low
1
@miket2e:好的,一个非常简单的例子:它允许您使用私有成员对两个对象进行比较。基本上我看不到它会造成伤害,并且它可以使语言复杂性、编译器工作和语言使用方面更加简便。 - Jon Skeet

9
这是因为C#强制实施类级别的私有性,而不是对象级别的私有性。
大多数主流语言都执行相同的策略,例如C#,C ++和Java。我认为原因是:
1)因为开发人员习惯于这种政策;
2)因为对象级别的私有性会变得非常繁琐,却换来很少的优势。

我认为不需要使用“this”,因为编译器能够区分成员变量。例如,为每个类成员值都需要getter方法会很繁琐。事实上,这将违反封装原则,因为您也会向其他类公开私有成员! - Simone
@miket2e 试着实现一个拷贝构造函数或者一个带有对象级别隐私的简单Equals函数。在当前的C#中非常简单:return this._somePrivateField.Equals(other._somePrivateField) - 如果你可以访问other._somePrivateField,那么如何在不添加公共getter的情况下实现这样的功能呢? - Michael Stum
1
@MichaelStum:将字段设置为实例私有或实例受保护的一个优点是,这样做可以清楚地表明派生类可以重新使用这些字段而不违反Liskov替换原则。否则,这种重新使用可能会导致LSP违规。例如,可以通过具有类私有Equals方法(接受其他对象的内容作为一堆离散参数或使用受保护类型),并且使得对象的公共Equals方法将其状态传递给其他对象的受保护equals方法来实现Equals - supercat

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