为什么私有字段属于该类型,而不是实例?

时间:2011-08-08 14:21:27

标签: c# oop language-design language-features private-members

在C#(和许多其他语言)中,访问相同类型的其他实例的私有字段是完全合法的。例如:

public class Foo
{
    private bool aBool;

    public void DoBar(Foo anotherFoo)
    {
        if (anotherFoo.aBool) ...
    }
}

由于C# specification(第3.5.1,3.5.2节)规定对私有字段的访问属于某种类型,而不是实例。我一直在与同事讨论这个问题,我们试图找出它之所以如此工作的原因(而不是限制对同一个实例的访问)。

我们可以提出的最佳参数是进行相等性检查,其中类可能希望访问私有字段以确定与另一个实例的相等性。还有其他原因吗?或者一些绝对意味着它必须像这样或某种东西工作的黄金理由是完全不可能的?

10 个答案:

答案 0 :(得分:72)

我认为这样做的一个原因是因为访问修饰符在编译时工作。因此,确定给定对象是否也是当前对象并不容易。例如,请考虑以下代码:

public class Foo
{
    private int bar;

    public void Baz(Foo other)
    {
        other.bar = 2;
    }

    public void Boo()
    {
        Baz(this);
    }
}

编译器是否必须确定other实际上是this?并非在所有情况下。有人可能认为这不应该编译,但这意味着我们有一个代码路径,其中无法访问正确实例的私有实例成员,我认为这更糟糕。

只需要类型级别而不是对象级别的可见性,可以确保问题易于处理,并且可以使

编辑:Danilel Hilgarth认为这种推理是倒退的确有其价值。语言设计者可以创建他们想要的语言,编译器编写者必须遵守它。话虽这么说,语言设计师确实有一些动力让编译器编写者更容易完成他们的工作。 (虽然在这种情况下,很容易争辩说私人成员可以通过this(隐式或显式)访问

但是,我认为这使问题更加混乱,而不是需要。如果上面的代码不起作用,大多数用户(包括我自己)会发现它不必要地限制:毕竟,那是我正在尝试访问的我的数据!我为什么要通过this

简而言之,我认为我可能夸大了编译器“难以”的情况。我真正意想不到的是,上述情况似乎是设计师希望工作的情况。

答案 1 :(得分:59)

因为在C#和类似语言中使用的封装类型 *的目的是降低不同代码片段(C#和Java中的类)的相互依赖性,而不是内存中的不同对象。

例如,如果您在一个使用另一个类中的某些字段的类中编写代码,那么这些类的耦合非常紧密。但是,如果您正在处理具有相同类的两个对象的代码,则没有额外的依赖性。一个班级总是取决于自己。

然而,所有关于封装的理论都会在有人创建属性(或Java中的get / set对)并直接暴露所有字段时失败,这会使类成为耦合,就好像它们正在访问字段一样。

*有关封装类型的说明,请参阅Abel的优秀答案。

答案 2 :(得分:49)

这个有趣的线程已经添加了相当多的答案,但是,我并没有找到为什么这种行为的真正原因。让我试一试:

回到日期

在80年代的Smalltalk与90年代中期的Java之间,面向对象的概念已经成熟。信息隐藏,最初并不被认为只是OO可用的概念(1978年首次提到),在Smalltalk中引入,因为类的所有数据(字段)都是私有的,所有方法都是公开的。在90年代OO的许多新发展过程中,Bertrand Meyer试图在他的具有里程碑意义的着作Object Oriented Software Construction (OOSC)中将大部分OO概念正式化,从那以后被认为是OO概念和语言设计的(几乎)权威性参考。 / p>

在私人可见性

的情况下

根据Meyer的说法,一个方法应该可用于一组已定义的类(第192-193页)。这显然提供了非常高的信息隐藏粒度,以下功能可用于classA和classB及其所有后代:

feature {classA, classB}
   methodName

private的情况下,他说以下内容:如果没有明确声明某个类型对其自己的类可见,则无法在限定的调用中访问该功能(方法/字段)。即如果x是变量,则不允许x.doSomething()。当然,在课堂内部允许不合格的访问。

换句话说:要允许同一个类的实例访问,您必须允许该类显式访问该类。这有时称为实例私有与私有。

编程语言中的Instance-private

我知道目前使用的至少两种语言使用实例私有信息隐藏而不是类私有信息隐藏。其中一种是艾尔,一种由迈耶设计的语言,它将OO置于极端的极端。另一种是Ruby,现在是一种更常见的语言。在Ruby中,private表示:"private to this instance"

语言设计的选择

有人建议允许实例私有对编译器来说很难。我不这么认为,因为只允许或禁止对方法进行合格调用相对简单。如果对于私有方法,doSomething()被允许而x.doSomething()不被允许,则语言设计者已经为私有方法和字段有效地定义了仅实例可访问性。

从技术角度来看,没有理由选择这样或那样的方式(特别是考虑到Eiffel.NET可以用IL做到这一点,即使有多重继承,没有内在的理由不提供此功能)

当然,这是一个品味问题,正如其他人已经提到的那样,如果没有私有方法和字段的类级别可见性的特性,相当一些方法可能更难编写。

为什么C#只允许类封装而不允许实例封装

如果你看一下实例封装上的互联网线程(一个术语有时用于指代语言在实例级别定义访问修饰符而不是类级别这一事实),这个概念经常被不满。但是,考虑到一些现代语言使用实例封装,至少对于私有访问修饰符,使您认为它可以在现代编程世界中使用。

然而,C#在语言设计方面对C ++和Java的看法最为严格。虽然Eiffel和Modula-3也在图片中,考虑到Eiffel遗失的许多功能(多重继承),我认为他们在私有访问修饰符时选择了与Java和C ++相同的路径。

如果你真的想知道为什么你应该试着抓住Eric Lippert,Krzysztof Cwalina,Anders Hejlsberg或其他任何从事C#标准工作的人。不幸的是,我在注释The C# Programming Language中找不到明确的注释。

答案 3 :(得分:18)

这只是我的意见,但实际上,我认为如果程序员可以访问类的源,您可以合理地信任它们访问类实例的私有成员。为什么在他们的左边你已经给了他们关键王国时,右手绑定程序员?

答案 4 :(得分:13)

原因确实是等式检查,比较,克隆,运算符重载...... 例如,在复数上实现operator +会非常棘手。

答案 5 :(得分:9)

首先,私有静态成员会发生什么?它们只能通过静态方法访问吗?你当然不希望这样,因为那时你将无法访问const

关于你的明确问题,请考虑StringBuilder的情况,它是作为自身实例的链接列表实现的:

public class StringBuilder
{
    private string chunk;
    private StringBuilder nextChunk;
}

如果您无法访问自己类的其他实例的私有成员,则必须实现ToString,如下所示:

public override string ToString()
{
    return chunk + nextChunk.ToString();
}

这会起作用,但它是O(n ^ 2) - 效率不高。事实上,这可能会破坏首先拥有StringBuilder课程的整个目的。如果可以访问您自己类的其他实例的私有成员,则可以通过创建适当长度的字符串来实现ToString,然后对每个块执行不安全的副本放在字符串中:

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),这使得它非常快,并且只有在您可以访问类的其他实例的私有成员时才能

答案 6 :(得分:3)

这在许多语言中都是完全合法的(C ++ for one)。访问修饰符来自OOP中的封装原则。我们的想法是限制对外部的访问,在这种情况下,外部是其他类。例如,C#中的任何嵌套类都可以访问它的父级私有成员。

虽然这是语言设计师的设计选择。对此访问的限制可能会使一些非常常见的情况复杂化,而不会对实体的隔离做出太大贡献。

有类似的讨论here

答案 7 :(得分:1)

我认为没有理由我们无法添加其他级别的隐私,其中数据对每个实例都是私有的。事实上,这甚至可以为语言提供一种完美的感觉。

但在实际操作中,我怀疑它真的有用。正如您所指出的,我们通常的私有性对于诸如等式检查之类的事情以及涉及Type的多个实例的大多数其他操作都是有用的。虽然,我也喜欢你关于维护数据抽象的观点,因为这是OOP中的一个重点。

我认为,提供以这种方式限制访问的能力可能是添加到OOP的一个很好的功能。它真的有用吗?我会说不,因为一个类应该能够信任自己的代码。由于该类是唯一可以访问私有成员的东西,因此在处理另一个类的实例时,没有理由需要数据抽象。

当然,您始终可以将代码编写为私有应用于实例。使用通常的get/set方法来访问/更改数据。如果类可能会受到内部更改,那么这可能会使代码更易于管理。

答案 8 :(得分:0)

上面给出了很好的答案。我想补充说,这个问题的一部分是事实上甚至允许在其自身内实例化一个类。它使得在递归逻辑“for”循环中,例如,只要你有逻辑来结束递归就使用那种类型的技巧。但是,在不创建这样的循环的情况下实例化或传递同一个类在逻辑上会创建自己的危险,即使它是一个被广泛接受的编程范例。例如,C#类可以在其默认构造函数中实例化自身的副本,但这不会破坏任何规则或创建因果循环。为什么呢?

BTW ....同样的问题也适用于“受保护”的成员。 :(

我从未完全接受过这种编程范式,因为它仍然带有一整套问题和风险,大多数程序员都没有完全掌握这些问题和风险,直到像这个问题出现问题并混淆了人们并且无视拥有私人成员的全部理由。

C#的这种“古怪和古怪”的方面还有一个原因,那就是优秀的编程与经验和技能无关,而只是知道技巧和陷阱......就像在汽车上工作一样。它的主张是规则被打破,这对于任何计算语言来说都是一个非常糟糕的模型。

答案 9 :(得分:-1)

在我看来,如果数据对于同一类型的其他实例是私有的,则它不一定是同一类型。它似乎不像其他实例那样表现或行为相同。可以根据私有内部数据轻松修改行为。这只会在我看来引起混乱。

总而言之,我个人认为编写从基类派生的类提供了与“每个实例拥有私有数据”相似的功能。相反,您只需为每个“唯一”类型设置一个新的类定义。