В C # (и многих других языках) совершенно законно обращаться к закрытым полям других экземпляров того же типа. Например:
public class Foo
{
private bool aBool;
public void DoBar(Foo anotherFoo)
{
if (anotherFoo.aBool) ...
}
}
Как указано в спецификации C # (разделы 3.5.1, 3.5.2), доступ к закрытым полям осуществляется по типу, а не по экземпляру. Я обсуждал это с коллегой, и мы пытаемся найти причину, по которой это работает так (а не ограничивать доступ к одному и тому же экземпляру).
Лучший аргумент, который мы могли бы выдвинуть, - это проверки на равенство, когда класс может захотеть получить доступ к закрытым полям, чтобы определить равенство с другим экземпляром. Есть ли другие причины? Или какая-то золотая причина, которая абсолютно означает, что она должна работать таким образом, или что-то совершенно невозможное?
Ответы:
Я думаю, что одна из причин, по которой он работает таким образом, заключается в том, что модификаторы доступа работают во время компиляции . Таким образом, определить, является ли данный объект текущим объектом или нет, нелегко. Например, рассмотрим этот код:
Может ли компилятор обязательно выяснить, что
other
есть на самом делеthis
? Не во всех случаях. Можно утверждать, что это просто не должно компилироваться, но это означает, что у нас есть путь к коду, где частный экземпляр экземпляра правильного экземпляра недоступен, что, я думаю, еще хуже.Только требование уровня типа, а не видимости уровня объекта гарантирует, что проблема поддается решению, а также создает ситуацию, которая, как кажется, должна работать, на самом деле работает.
РЕДАКТИРОВАТЬ : точка зрения Данил Хилгарт, что это рассуждение назад, имеет свои достоинства. Разработчики языка могут создать язык, который они хотят, и авторы компилятора должны соответствовать ему. При этом у языковых дизайнеров есть некоторый стимул, чтобы облегчить работу компиляторов. (Хотя в этом случае достаточно легко утверждать, что к закрытым членам можно было получить доступ только через
this
(неявно или явно)).Тем не менее, я считаю, что это делает проблему более запутанной, чем она должна быть. Большинство пользователей (включая меня) считают это ненужным ограничением, если вышеуказанный код не работает: в конце концов, это мои данные, к которым я пытаюсь получить доступ! Почему я должен пройти
this
?Короче говоря, я думаю, что, возможно, преувеличил случай, потому что это было "трудно" для компилятора. Что я действительно хотел донести, так это то, что вышеприведенная ситуация кажется такой, что дизайнеры хотели бы получить работу.
источник
this.bar = 2
но неother.bar = 2
потому, что этоother
мог бы быть другой экземпляр.Поскольку цель типа инкапсуляции, используемой в C # и подобных языках *, состоит в том, чтобы уменьшить взаимную зависимость различных частей кода (классов в C # и Java), а не различных объектов в памяти.
Например, если вы пишете код в одном классе, который использует несколько полей в другом классе, то эти классы очень тесно связаны. Однако, если вы имеете дело с кодом, в котором у вас есть два объекта одного класса, то никакой дополнительной зависимости нет. Класс всегда зависит от самого себя.
Однако вся эта теория об инкапсуляции не работает, как только кто-то создает свойства (или получает / устанавливает пары в Java) и выставляет все поля напрямую, что делает классы такими же связанными, как если бы они в любом случае обращались к полям.
* Для уточнения видов инкапсуляции см. Отличный ответ Авеля.
источник
get/set
методы предоставляются. Методы доступа по-прежнему поддерживают абстракцию (пользователю не нужно знать, что за ним есть поле или какие поля могут быть за свойством). Например, если мы пишемComplex
класс, мы можем выставить свойства, чтобы получить / установить полярные координаты, а другой получить / установить для декартовых координат. Какой класс использует класс, неизвестно пользователю (это может быть что-то совершенно другое).private
. Вы можете проверить мой ответ на мой взгляд на вещи, если хотите;).В эту интересную ветку уже было добавлено довольно много ответов, однако я не совсем нашел реальную причину, почему это поведение так, как есть. Позвольте мне попробовать:
В те дни
Где-то между Smalltalk в 80-х и Java в середине 90-х созрела концепция объектной ориентации. Сокрытие информации, изначально не рассматриваемое как концепция, доступная только для ОО (впервые упомянутое в 1978 г.), было введено в Smalltalk, поскольку все данные (поля) класса являются частными, все методы являются открытыми. Во время многих новых разработок ОО в 90-х годах Бертран Мейер попытался формализовать большую часть концепций ОО в своей знаковой книге Построение объектно-ориентированного программного обеспечения (OOSC), которая с тех пор считается (почти) окончательной ссылкой на концепции ОО и проектирование языка. ,
В случае частной видимости
Согласно Мейеру, метод должен быть доступен для определенного набора классов (стр. 192-193). Это, очевидно, дает очень высокую степень детализации сокрытия информации, следующая функция доступна для classA и classB и всех их потомков:
В случае
private
он говорит следующее: без явного объявления типа как видимого для его собственного класса, вы не можете получить доступ к этой функции (метод / поле) в квалифицированном вызове. Т.е. еслиx
это переменная,x.doSomething()
не допускается. Безусловный доступ разрешен, конечно, внутри самого класса.Другими словами: чтобы разрешить доступ экземпляру того же класса, вы должны явно разрешить доступ к методу этого класса. Это иногда называют частным экземпляром против частного класса.
Частный экземпляр в языках программирования
Я знаю по крайней мере два языка, которые в настоящее время используются, которые используют скрытие частной информации экземпляра, а не скрытие частной информации класса. Одним из них является Eiffel, язык, разработанный Мейером, который доводит ОО до предела. Другой - Ruby, гораздо более распространенный язык в наши дни. В Ruby
private
означает «личное для данного экземпляра» .Выбор для языкового дизайна
Было высказано предположение, что разрешение экземпляра private будет сложным для компилятора. Я так не думаю, так как достаточно просто разрешить или запретить квалифицированные вызовы методов. Если для частного метода,
doSomething()
разрешено иx.doSomething()
не , разработчик языка эффективно определил доступность только для экземпляра для закрытых методов и полей.С технической точки зрения, нет причин выбирать тот или иной путь (особенно если учесть, что Eiffel.NET может делать это с IL, даже с множественным наследованием, нет внутренней причины не предоставлять эту функцию).
Конечно, это дело вкуса, и, как уже упоминали другие, довольно сложно написать некоторые методы без возможности видимости на уровне класса частных методов и полей.
Почему C # допускает только инкапсуляцию классов, а не инкапсуляцию экземпляров
Если вы посмотрите на интернет-потоки на инкапсуляцию экземпляра (термин, который иногда используется для обозначения того факта, что язык определяет модификаторы доступа на уровне экземпляра, а не на уровне класса), концепция часто осуждается. Однако, учитывая, что некоторые современные языки используют инкапсуляцию экземпляров, по крайней мере, для модификатора частного доступа, вы можете думать, что это может быть и полезно в современном мире программирования.
Тем не менее, C #, по общему признанию, больше всего смотрел на C ++ и Java из-за своего языкового дизайна. Хотя Eiffel и Modula-3 также были в картине, учитывая, что многие функции Eiffel отсутствуют (множественное наследование), я считаю, что они выбрали тот же маршрут, что и Java и C ++, когда дело дошло до модификатора частного доступа.
Если вы действительно хотите знать, почему вы должны попытаться заполучить Эрика Липперта, Кшиштофа Квалину, Андерса Хейлсберга или любого другого, кто работал над стандартом C #. К сожалению, я не смог найти однозначного примечания в аннотированном языке программирования C # .
источник
Foo
эффективно представляло интерфейс и реализующий класс; поскольку в COM не было понятия ссылки на объект класса - просто ссылка на интерфейс - класс не мог содержать ссылку на что-то, что гарантированно является другим экземпляром этого класса. Этот дизайн на основе интерфейса сделал некоторые вещи громоздкими, но это означало, что можно было разработать класс, который мог бы заменить другой, без необходимости иметь какие-либо одинаковые внутренние компоненты.Это только мое мнение, но прагматично, я думаю, что если у программиста есть доступ к источнику класса, вы можете разумно доверять им доступ к закрытым членам экземпляра класса. Зачем связывать программистов правой рукой, когда слева от них вы уже дали им ключи от королевства?
источник
Причина действительно в проверке равенства, сравнении, клонировании, перегрузке операторов ... Было бы очень сложно реализовать оператор +, например, на комплексных числах.
источник
readonly
и тогда, что также относится и к объявленному экземпляру. Очевидно, вы утверждаете, что должно быть специальное правило компилятора, чтобы запретить это, но каждое такое правило - это функция, которую команда C # должна определять, документировать, локализовывать, тестировать, поддерживать и поддерживать. Если нет убедительных преимуществ, то зачем им это делать?Прежде всего, что будет с частными статическими членами? Доступ к ним возможен только статическими методами? Вы, конечно, не захотите этого, потому что тогда вы не сможете получить доступ к своим
const
.Что касается вашего явного вопроса, рассмотрим случай a
StringBuilder
, который реализован в виде связанного списка его экземпляров:Если вы не можете получить доступ к закрытым членам других экземпляров вашего собственного класса, вы должны реализовать
ToString
это так:Это будет работать, но это O (n ^ 2) - не очень эффективно. На самом деле, это, вероятно, побеждает всю цель иметь
StringBuilder
класс в первую очередь. Если вы можете получить доступ к закрытым членам других экземпляров вашего собственного класса, вы можете реализовать этоToString
, создав строку правильной длины, а затем выполнив небезопасную копию каждого куска в соответствующем месте строки:Эта реализация O (n), что делает его очень быстрым и возможен, только если у вас есть доступ к закрытым членам других экземпляров вашего класса .
источник
internal
делает его менее приватным! Вы бы в конечном итоге подвергли внутренности вашего класса всему, что находится в его сборке.Это вполне законно во многих языках (C ++ для одного). Модификаторы доступа происходят из принципа инкапсуляции в ООП. Идея состоит в том, чтобы ограничить доступ извне , в этом случае извне есть другие классы. Например, любой вложенный класс в C # может получить доступ к частным членам своих родителей.
Пока это выбор дизайна для языкового дизайнера. Ограничение этого доступа может чрезвычайно усложнить некоторые очень распространенные сценарии, не внося большой вклад в изоляцию объектов.
Существует аналогичное обсуждение здесь
источник
Я не думаю, что есть причина, по которой мы не можем добавить еще один уровень конфиденциальности, когда данные являются частными для каждого экземпляра. На самом деле, это может даже дать хорошее ощущение полноты языка.
Но в реальной практике я сомневаюсь, что это было бы действительно полезно. Как вы указали, наша обычная приватность полезна для таких вещей, как проверки на равенство, а также для большинства других операций, связанных с несколькими экземплярами типа. Хотя мне также нравится ваше мнение о поддержании абстракции данных, так как это важный момент в ООП.
Я думаю, что возможность ограничить доступ таким способом могла бы стать хорошей возможностью для добавления в ООП. Это действительно так полезно? Я бы сказал, нет, так как класс должен быть в состоянии доверять своему собственному коду. Поскольку этот класс является единственной вещью, которая может получить доступ к закрытым членам, нет никакой реальной причины нуждаться в абстракции данных при работе с экземпляром другого класса.
Конечно, вы всегда можете написать свой код, как если бы он был закрытым для экземпляров. Используйте обычные
get/set
методы для доступа / изменения данных. Это, вероятно, сделает код более управляемым, если класс будет подвержен внутренним изменениям.источник
Отличные ответы, данные выше. Я хотел бы добавить, что частью этой проблемы является тот факт, что создание экземпляра класса внутри себя даже разрешено в первую очередь. Например, в рекурсивной логике цикла «for» он использует трюк такого типа, если у вас есть логика для завершения рекурсии. Но создание или передача одного и того же класса внутри себя без создания таких циклов логически создает свои опасности, хотя это широко распространенная парадигма программирования. Например, класс C # может создавать свою копию в конструкторе по умолчанию, но это не нарушает никаких правил и не создает причинно-следственные связи. Зачем?
Кстати ... эта же проблема относится и к "защищенным" членам. :(
Я никогда не принимал эту парадигму программирования полностью, потому что она по-прежнему сопряжена с целым набором проблем и рисков, которые большинство программистов не понимают до тех пор, пока такие проблемы, как эта проблема, не поднимаются и не сбивают с толку людей и не бросают вызов всей причине наличия частных членов.
Этот «странный и дурацкий» аспект C # - еще одна причина, почему хорошее программирование не имеет ничего общего с опытом и навыками, а просто знает хитрости и ловушки… как работа на автомобиле. Это аргумент, что правила должны были быть нарушены, что является очень плохой моделью для любого вычислительного языка.
источник
Мне кажется, что если бы данные были частными для других экземпляров того же типа, они больше не обязательно были бы того же типа. Казалось бы, он не ведет себя так же, как другие экземпляры. Поведение может быть легко изменено на основе этих частных внутренних данных. По моему мнению, это только распространит замешательство.
Грубо говоря, я лично считаю, что написание классов, производных от базового класса, предлагает аналогичную функциональность, которую вы описываете как «наличие личных данных на экземпляр». Вместо этого у вас просто есть новое определение класса для каждого «уникального» типа.
источник