为什么私人领域是私人的types,而不是实例?
在C#(以及许多其他语言)中,访问相同types的其他实例的私有字段是完全合法的。 例如:
public class Foo { private bool aBool; public void DoBar(Foo anotherFoo) { if(anotherFoo.aBool) ... } }
由于C#规范 (3.5.1节,3.5.2节)规定私有字段的访问是一种types,而不是一个实例。 我一直在和一位同事讨论这个问题,我们试图想出一个这样的原因(而不是限制对同一个实例的访问)。
我们可以提出的最好的论点是平等检查,其中类可能想访问私人领域,以确定与另一个实例的平等。 还有其他的原因吗? 或者一些绝对意味着它必须这样或那样的工作的黄金理由是完全不可能的?
我认为这样做的一个原因是因为访问修饰符在编译时工作。 因此,确定给定对象是否也是当前对象是不容易的。 例如,考虑这个代码:
public class Foo { private int bar; public void Baz(Foo other) { other.bar = 2; } public void Boo() { Baz(this); } }
编译器一定能弄清楚other
是this
吗? 并不是所有的情况。 有人可能会说,这不应该编译,但是这意味着我们有一个代码path,其中正确实例的私有实例成员不可访问,我认为更糟糕的是。
只需要types级别而不是对象级别的可视性,就可以确保问题是可以处理的,并且可以使情况看起来像应该工作。
编辑 :Danilel Hilgarth指出,这个推理是倒退是有优点的。 语言devise者可以创build他们想要的语言,编译器编写者必须遵守它。 话虽如此,语言devise师确实有一些动机让编译器编写者更容易完成工作。 (尽pipe在这种情况下,很容易争辩说私人成员只能通过this
(隐式或显式地)访问)。
但是,我认为这使得问题比需要更加混乱。 如果上面的代码不起作用,大多数用户(包括我自己)都会觉得不必要的限制:毕竟,这是我想要访问的数据! 为什么我必须经历this
?
总之,我想我可能夸大了编译器的“困难”情况。 我真正想要得到的是,上面的情况似乎是devise师希望有工作的一种情况。
因为在C#和类似语言 *中使用的封装types的目的是降低不同代码段(C#和Java中的类)的相互依赖性,而不是内存中的不同对象。
例如,如果您在一个使用另一个类中的某个字段的类中编写代码,那么这些类就会紧密耦合。 但是,如果您正在处理具有相同类的两个对象的代码,则不存在额外的依赖关系。 一个class总是依靠自己。
然而,只要有人创build属性(或Java中的get / set对)并直接暴露所有的字段,所有关于封装的理论都会失败,这使得类如同访问字段一样耦合。
*关于封装types的说明见亚伯的优秀答案。
相当多的答案已经被添加到这个有趣的线程,但是,我不完全find为什么这种行为是这样的真正原因。 让我试试看:
回到那个时代
在80年代的Smalltalk和90年代中期的Java之间,面向对象的概念已经成熟了。 信息隐藏并不是最初被认为是OO(1978年提到的第一个)的概念,而是在Smalltalk中引入的,因为一个类的所有数据(字段)都是私有的,所有的方法都是公开的。 在90年代面向对象的许多新发展中,Bertrand Meyer试图将他的里程碑式的面向对象软件构造(OOSC)中的许多面向对象的概念正式化,从那以后被认为是(几乎)关于面向对象概念和语言devise。
在私人知名度的情况下
根据Meyer的说法,一种方法应该可用于一组定义的类(第192-193页)。 这明显给出了非常高的信息隐藏粒度,以下function可用于classA和classB及其所有后代:
feature {classA, classB} methodName
在private
的情况下,他说:如果没有明确声明一个类对其自己的类是可见的,那么你不能在合格的调用中访问该特性(方法/字段)。 即如果x
是一个variables, x.doSomething()
是不允许的。 当然,在class级本身内部,不允许进入。
换句话说:为了允许同一个类的实例访问,你必须明确地允许该类访问该方法。 这有时被称为实例私有与类私有。
实例在编程语言中是私有的
我知道目前至less有两种语言使用实例隐私信息隐藏而不是类隐私信息隐藏。 一个是艾尔,这是迈尔devise的一种语言,把OO带到了极致。 另一个是Ruby,这是现在比较常见的语言。 在Ruby中, private
表示: “对这个实例是私有的” 。
语言devise的select
有人build议允许编译器很难实例私有。 我不这么认为,因为只允许或禁止合格的方法调用是相对简单的。 如果对于私有方法, doSomething()
是允许的而x.doSomething()
则不是,语言devise者已经有效地为私有方法和字段定义了实例唯一的可访问性。
从技术angular度来看,没有理由select某种方式(特别是当考虑到Eiffel.NET可以用IL做到这一点,即使有多重inheritance,也没有不提供这个特性的固有原因)。
当然,这是一个口味的问题,正如其他人已经提到的,如果没有私有方法和领域的类级可见性的特征,很多方法可能会更难写。
为什么C#只允许类封装而不是实例封装
如果你在实例封装上看networking主题(这个术语有时候被用来指代一个语言在实例层次上定义了访问修饰符,而不是类层次),这个概念经常被忽视。 但是,考虑到一些现代语言使用实例封装,至less对于私有访问修饰符而言,使您认为它可以在现代编程世界中使用。
但是,C#在语言devise上的确看起来在C ++和Java上看起来最为困难。 虽然艾菲尔和Modula-3也在图片中,考虑到埃菲尔缺失(多重inheritance)的许多特点,我相信他们select了与Java和C ++相同的路线,当谈到私人访问修改器。
如果你真的想知道为什么你应该试着抓住Eric Lippert,Krzysztof Cwalina,Anders Hejlsberg或其他任何从事C#标准工作的人。 不幸的是,我在注释的C#编程语言中找不到明确的注释。
这只是我的看法,但实际上,我认为,如果一个程序员可以访问类的来源,那么可以合理地信任他们访问类实例的私有成员。 为什么要在程序员的左边绑定一个程序员,你已经给了他们王国的钥匙?
其原因的确是平等检查,比较,克隆,运算符重载。例如,在复数上实现运算符+是非常棘手的。
首先,私人静态会员会发生什么? 他们只能通过静态方法访问吗? 你当然不会想要,因为那样你就无法访问你的const
。
至于你明确的问题,考虑一个StringBuilder
的情况,它被实现为它自己的实例的链表:
public class StringBuilder { private string chunk; private StringBuilder nextChunk; }
如果你不能访问你自己类的其他实例的私有成员,你必须像这样实现ToString
:
public override string ToString() { return chunk + nextChunk.ToString(); }
这将工作,但它是O(n ^ 2) – 不是很有效。 实际上,这可能会首先破坏一个StringBuilder
类的全部目的。 如果可以访问自己类的其他实例的私有成员,则可以通过创build适当长度的string来实现ToString
,然后将每个块的不安全副本发送到string中的适当位置:
public override string ToString() { string ret = string.FastAllocateString(Length); StringBuilder next = this; unsafe { fixed (char *dest = ret) while (next != null) { fixed (char *src = next.chunk) string.wstrcpy(dest, src, next.chunk.Length); next = next.nextChunk; } } return ret; }
这个实现是O(n),这使得它非常快速, 只有当你有权访问你的类的其他实例的私有成员时才有可能 。
这在许多语言中是完全合法的(C ++ for one)。 访问修饰符来自OOP中的封装原则。 这个想法是限制访问外面 ,在这种情况下外面是其他类。 例如C#中的任何嵌套类都可以访问它的父亲私有成员。
虽然这是一个语言devise师的deviseselect。 这种访问的限制可能会使一些非常常见的情况变得复杂,而不会对实体的隔离作出太大的贡献。
这里有一个类似的讨论
我不认为有一个原因,我们不能添加另一个级别的隐私,其中数据是每个实例私有的。 实际上,这甚至可以给语言提供一个完整的感觉。
但在实际操作中,我怀疑这是否真的有用。 正如你所指出的那样,我们通常的私有性对于诸如平等检查之类的事情以及涉及多个Type的实例的大多数其他操作是有用的。 虽然,我也喜欢你关于维护数据抽象的观点,因为这是OOP中的重要一点。
我认为,提供以这种方式限制访问权限的能力可能是添加到OOP的一个很好的function。 这真的有用吗? 我会说不,因为一个类应该能够相信自己的代码。 既然这个类是唯一可以访问私有成员的东西,那么在处理另一个类的实例时就不需要数据抽象了。
当然,您可以随时编写代码,就好像专用于实例一样。 使用通常的get/set
方法来访问/更改数据。 这可能会使代码更容易pipe理,如果类可能会受到内部的变化。
上面给出的好答案。 我会补充说,这个问题的一部分是,实例化一个类内部甚至允许在一开始。 它使得在一个recursion逻辑“for”循环中,例如,只要你有逻辑来结束recursion就使用这种types的技巧。 但是,在不创build这样的循环的情况下实例化或传递相同的类在逻辑上会产生自己的危险,尽pipe它是一个被广泛接受的编程范例。 例如,一个C#类可以在默认的构造函数中实例化一个自身的副本,但是这不会破坏任何规则或创build因果循环。 为什么?
顺便说一句….同样的问题也适用于“受保护的”成员。 🙁
我从来没有完全接受这种编程模式,因为它仍然伴随着大多数程序员不能完全掌握的一整套问题和风险,直到这样的问题出现并混淆了人们,并且蔑视私有成员的全部理由。
C#的这个“古怪而古怪”的方面,正是编程与经验和技巧无关的又一个原因,只是知道这些技巧和陷阱,比如在汽车上工作。 它的规则是打算被打破,这是任何计算机语言的一个非常糟糕的模式的论点。
在我看来,如果数据对于同一types的其他实例是私有的,则不一定是相同的types。 它似乎不像其他实例一样行为或行为相同。 行为可以很容易地根据私人内部数据进行修改。 这只会在我看来混乱。
松散地说,我个人认为编写从基类派生的类提供了类似的function,你正在描述“每个实例有私有数据”。 相反,你只是每个“唯一”types有一个新的类定义。