Почему частные поля являются частными для типа, а не для экземпляра?


В C# (и многих других языках) совершенно законно получать доступ к закрытым полям других экземпляров того же типа. Например:

public class Foo
{
    private bool aBool;

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

Как спецификация C# (разделы 3.5.1, 3.5.2) состояния доступ к частным полям находится на типе, а не на экземпляре. Я обсуждал это с коллегой и мы пытаемся придумать, почему это работает так (а не ограничивать доступ к одному экземпляру).

лучший аргумент мы могли бы придумать is для проверки равенства, где класс может захотеть получить доступ к частным полям, чтобы определить равенство с другим экземпляром. Есть ли другие причины? Или какая-то золотая причина, которая абсолютно означает, что она должна работать так, или что-то будет совершенно невозможно?

10 147

10 ответов:

Я думаю, что одна из причин, по которой он работает таким образом, заключается в том, что модификаторы доступа работают в время компиляции. Таким образом, определение того, является ли данный объект также настоящее объект не так легко сделать. Например, рассмотрим следующий код:

public class Foo
{
    private int bar;

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

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

может ли компилятор обязательно выяснить, что other на самом деле this? Не во всех случаях. Можно утверждать, что это просто не должно компилироваться тогда, но это означает, что у нас есть путь кода, где частный экземпляр член правильного экземпляра не доступен, что, я думаю, еще хуже.

только требуя видимость на уровне типа, а не на уровне объекта, гарантирует, что проблема поддается решению, а также создает ситуацию, которая кажется ей должны работа на самом деле работа.

EDIT: точка зрения Даниэля Хилгарта о том, что это рассуждение является обратным, действительно заслуживает внимания. Языковые дизайнеры могут создавать язык, который они хотят, и составители компиляторов должны соответствовать ему. Тем не менее, у языковых дизайнеров есть некоторый стимул, чтобы облегчить компиляторам свою работу. (Хотя в этом случае достаточно легко утверждать, что частные члены могли бы тогда только через this (явно или неявно)).

однако я считаю, что это делает проблему более запутанной, чем это должно быть. Большинство пользователей (включая меня) сочтут это излишне ограничивающим, если приведенный выше код не получилось: ведь это мой данные, к которым я пытаюсь получить доступ! Почему я должен пройти через this?

короче говоря, я думаю, что я, возможно, преувеличил дело за то, что это "трудно" для компилятора. То, что я действительно хотел донести, - это то, что выше ситуация кажется такой, что дизайнеры хотели бы иметь работу.

потому что цель тип инкапсуляции, используемый в C# и подобных языках* - это снижение взаимной зависимости различных частей кода (классов в C# и Java), а не различных объектов в памяти.

например, если вы пишете код в одном классе, который использует некоторые поля в другом классе, то эти классы очень тесно связаны. Однако, если вы имеете дело с кодом, в котором у вас есть два объекта одного класса, то есть никаких дополнительных зависимостей. Один класс всегда зависит от самого.

однако вся эта теория об инкапсуляции терпит неудачу, как только кто-то создает свойства (или пары get/set в Java) и предоставляет все поля напрямую, что делает классы такими же связанными, как если бы они все равно обращались к полям.

*для разъяснения видов инкапсуляции см. Отличный ответ Абеля.

некоторые ответы уже были добавлены в эту интересную тему, Однако я не совсем нашел настоящую причину почему это поведение так и есть. Позвольте мне попробовать:

назад в те дни

где-то между Smalltalk в 80-х и Java в середине 90-х концепция объектной ориентации созрела. Сокрытие информации, первоначально не считавшееся концепцией, доступной только ОО (впервые упомянутой в 1978 году), было введено в Smalltalk поскольку все данные (поля) класса являются частными, все методы являются общедоступными. Во время многих новых разработок ОО в 90-х годах Бертран Мейер попытался формализовать большую часть концепций ОО в своей эпохальной книге объектно-ориентированное программное обеспечение (OOSC) который с тех пор считается (почти) окончательной ссылкой на концепции ОО и языковой дизайн.

в случае частной видимости

согласно Meyer метод должен быть сделан доступно для определенного набора классов (стр. 192-193). Это дает, очевидно, очень высокую степень детализации сокрытия информации, следующая функция доступна для classA и classB и всех их потомков:

feature {classA, classB}
   methodName

в случае private Он говорит следующее: без явного объявления типа как видимого для своего собственного класса, вы не можете получить доступ к этой функции (метод/поле) в квалифицированном вызове. То есть если x - это переменная, x.doSomething() не допускается. Неквалифицированный доступ есть допускается, конечно, внутри самого класса.

другими словами: чтобы разрешить доступ к экземпляру того же класса, вы должны разрешить доступ к методу этого класса явно. Это иногда называют экземпляром-частным по сравнению с классом-частным.

экземпляр-частный в языках программирования

Я знаю, по крайней мере, два языка в настоящее время используются, которые используют экземпляр-скрытие частной информации в отличие от класса-скрытие частной информации. Один музей, а язык, разработанный Мейером, который принимает ОО до крайних пределов. Другой-Руби, гораздо более распространенный язык в наши дни. В Руби,private означает: "частный для этого экземпляра".

выбор для дизайна язык

было высказано предположение, что разрешить экземпляр-private будет трудно для компилятора. Я так не думаю, поскольку относительно просто разрешить или запретить квалифицированные вызовы методов. Если для частного метода doSomething() разрешена и x.doSomething() нет, языковой дизайнер фактически определил доступность только для экземпляра для частных методов и полей.

С технической точки зрения, нет никаких причин выбирать тот или иной путь (esp. при рассмотрении этого Eiffel.NET можно сделать это с помощью IL, даже с множественным наследованием, нет никакой внутренней причины не предоставлять эту функцию).

конечно, это дело вкуса и, как уже упоминалось, довольно некоторые методы могут быть сложнее написать без возможности видимости на уровне класса частных методов и полей.

почему C# допускает только инкапсуляцию класса, а не инкапсуляцию экземпляра

если вы посмотрите на интернет-потоки при инкапсуляции экземпляра (термин, иногда используемый для обозначения того, что язык определяет модификаторы доступа на уровне экземпляра, а не на уровне класса), эта концепция часто не одобряется. Однако, учитывая, что некоторые современные языки используют инкапсуляцию экземпляра, по крайней мере для модификатор доступа private, заставляет вас думать, что это может быть и полезно в современном мире программирования.

тем не менее, C#, по общему признанию, больше всего смотрел на C++ и Java для его языкового дизайна. Хотя Eiffel и Modula-3 также были на картинке, учитывая многие особенности Eiffel missing (множественное наследование), я считаю, что они выбрали тот же маршрут, что и Java и C++, когда дело дошло до модификатора частного доступа.

если вы действительно хотите знать почему вы должны попытаться связаться с Эриком Липпертом, Кшиштофом Квалиной, Андерсом Хейлсбергом или кем-либо еще, кто работал над стандартом C#. К сожалению, я не смог найти окончательную заметку в аннотированном Язык Программирования C#.

Это только мое мнение, но прагматически я думаю, что если программист имеет доступ к источнику класса, вы можете разумно доверять им доступ к закрытым членам экземпляра класса. Зачем связывать программистов правой рукой, когда в левой вы уже дали им ключи от королевства?

причина действительно является проверка равенства, сравнение, клонирование, перегрузка оператора... Было бы очень сложно реализовать operator+ на комплексных числах, например.

прежде всего, что произойдет с частными статическими членами? Могут ли они быть доступны только статическими методами? Вы, конечно, не хотели бы этого, потому что тогда вы не сможете получить доступ к своему const s.

Что касается вашего явного вопроса, рассмотрим случай a 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), что делает это очень быстро, и это возможно только при наличии доступа к закрытым членам других экземпляров вашего класса.

Это совершенно законно во многих языках (C++ для одного). Модификаторы доступа исходят из принципа инкапсуляции в ООП. Идея в том, чтобы ограничить доступ к за пределами в этом случае снаружи другие классы. Любой вложенный класс В C#, например, может получить доступ к своим закрытым членам-родителям.

хотя это выбор дизайна для языкового дизайнера. Ограничение этого доступа может чрезвычайно усложнить некоторые очень распространенные сценарии, не внося большого вклада к изоляции сущностей.

есть аналогичная дискуссия здесь

Я не думаю, что есть причина, почему мы не можем добавить еще один уровень конфиденциальности, где данные каждого экземпляра. На самом деле, это может даже обеспечить приятное чувство полноты языка.

но на практике, я сомневаюсь, что это действительно будет полезно. Как вы отметили, наша обычная приватность полезна для таких вещей, как проверки равенства, а также для большинства других операций, связанных с несколькими экземплярами типа. Хотя, мне также нравится ваша точка зрения о поддержание абстракции данных, поскольку это важный момент в ООП.

Я думаю, что все вокруг, предоставляя возможность ограничить доступ таким образом, может быть хорошей функцией для добавления в ООП. Это действительно так полезно? Я бы сказал Нет, так как класс должен быть в состоянии доверять своему собственному коду. Поскольку этот класс является единственным, что может получить доступ к закрытым членам, нет никакой реальной причины для необходимости абстракции данных при работе с экземпляром другого класса.

конечно, вы всегда можете напишите свой код как будто private применяется к экземплярам. Используйте обычный get/set методы доступа / изменения данных. Это, вероятно, сделает код более управляемым, если класс может быть подвержен внутренним изменениям.

отличные ответы, приведенные выше. Я бы добавил, что часть этой проблемы заключается в том, что создание экземпляра класса внутри себя даже допускается в первую очередь. Это делает так как в рекурсивной логике" для " цикла, например, чтобы использовать этот тип трюка до тех пор, пока у вас есть логика, чтобы закончить рекурсию. Но создание экземпляра или передача одного и того же класса внутри себя без создания таких циклов логически создает свои собственные опасности, хотя его широко принятая парадигма программирования. Например, с# Класс может создать копию самого себя в своем конструкторе по умолчанию, но это не нарушает никаких правил или не создает причинных циклов. Зачем?

кстати....эта же проблема относится и к" защищенным " членам. : (

Я никогда не принимал эту парадигму программирования полностью, потому что она все еще поставляется с целым набором проблем и рисков, которые большинство программистов не полностью понимают, пока такие проблемы, как эта проблема, не поднимаются и не смущают людей и не бросают вызов всей причине наличия частного члены.

Мне кажется, что если бы данные были частными для других экземпляров того же типа, это не обязательно был бы тот же тип больше. Казалось бы, он не будет вести себя или действовать так же, как в других случаях. Поведение может быть легко изменено на основе этих частных внутренних данных. Это только посеяло бы путаницу, на мой взгляд.

грубо говоря, я лично думаю, что написание классов, производных от базового класса, предлагает аналогичную функциональность, которую вы описываете с помощью " иметь личные данные для каждого пример'. Вместо этого у вас просто есть новое определение класса для "уникального" типа.