Почему C# не предоставляет ключевое слово friend в стиле C++?

Ключевое слово друга C++ позволяет class A назначать class B своим другом. Это позволяет Class B получить доступ к членам private / protectedclass A.

Я никогда не читал ничего о том, почему это было исключено из C# (и VB.NET). В большинстве ответов на этот предыдущий вопрос StackOverflow, похоже, говорится, что это полезная часть C++ и есть веские причины для ее использования. По моему опыту, я должен согласиться.

Другой вопрос, как мне кажется, действительно спрашивает, как сделать что-то похожее на friend в приложении C#. Хотя ответы обычно вращаются вокруг вложенных классов, это не кажется таким элегантным, как использование ключевого слова friend.

Исходный Книга "Паттерны дизайна" регулярно использует его в своих примерах.

Итак, вкратце, почему friend отсутствует в C#, и каков «наилучший» способ (или способы) его моделирования на C#?

(Кстати, ключевое слово internal - это нет, то же самое, оно позволяет классам все внутри всей сборки получать доступ к членам internal, в то время как friend позволяет вам передать определенный класс полный доступ другому классу ровно один)

я чувствую себя защищенным изнутри - хороший компромисс.

Gulzar Nazim 15.10.2008 07:39

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

Ash 15.10.2008 07:40

На это легко ответить: C# предлагает «внутренний» в качестве модификатора доступа, который предоставляет доступ ко всему коду в том же модуле / сборке. Это устраняет потребность в чем-то вроде друга. В Java ключевое слово «protected» ведет себя аналогично w.r.t. доступ из того же пакета.

sellibitze 16.02.2010 16:19

@sellibitze, отличия от internal упоминаю в вопросе. Проблема с Interanl заключается в том, что все классы в сборке могут обращаться к внутренним членам. Это нарушает инкапсуляцию, поскольку многим из этих классов может не потребоваться доступ.

Ash 17.02.2010 03:44
"почему это было исключено из C#" -- By saying that, the OP implicitly compares C# to C++. But why should C++ be the measuring tape for C#? Why should C# have to be anything like C++? It мог be... but it simply isn't.
stakx - no longer contributing 21.10.2012 13:29

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

danswain 23.10.2009 17:01

Потому что у Microsoft не было философии, что ваши друзья должны видеть ваши личные части.

jaybers 23.08.2015 05:22

Не спешите отказываться от internal. Мой опыт показывает, что дружеские отношения обычно возникают между двумя тесно связанными классами (список и узел, фабрика и виджет и т. д.), Которые удобно разделить на отдельную сборку. Возможно, это немного странно, что ваши два списка могут иметь доступ к узлам друг друга, но это не значит, что вам нужно делать эти свойства или методы доступными для внешнего / клиентского кода. Это немного странно для людей, которые привыкли писать отдельные монолитные проекты, но на самом деле разделение сборок дает много преимуществ.

GrandOpener 03.10.2015 22:58
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
211
8
58 143
22
Перейти к ответу Данный вопрос помечен как решенный

Ответы 22

Вы можете приблизиться к "другу" C++ с помощью ключевого слова C# "внутренний".

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

Ash 15.10.2008 07:35

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

Konrad Rudolph 21.01.2010 15:15

Я подозреваю, что это как-то связано с моделью компиляции C# - построением IL JIT-компиляции во время выполнения. то есть: по той же причине, по которой дженерики C# фундаментально отличаются от дженериков C++.

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

Ash 15.10.2008 07:39

Вы должны иметь возможность выполнять те же действия, для которых "друг" используется в C++, используя интерфейсы в C#. Это требует, чтобы вы явно определяли, какие члены передаются между двумя классами, что является дополнительной работой, но также может облегчить понимание кода.

Если у кого-то есть пример разумного использования слова «друг», который нельзя смоделировать с помощью интерфейсов, поделитесь им! Я хотел бы лучше понять разницу между C++ и C#.

Хорошая точка зрения. У вас была возможность взглянуть на более ранний вопрос SO (заданный монооксидом и связанный выше?) Мне было бы интересно, как лучше всего решить это на C#?

Ash 15.10.2008 07:44

Я не совсем уверен, как это сделать на C#, но я думаю, что ответ Джона Скита на вопрос о монооксиде указывает в правильном направлении. C# допускает вложенные классы. Однако я на самом деле не пробовал кодировать и компилировать решение. :)

Parappa 15.10.2008 07:53

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

Vaccano 14.12.2009 22:13

Проблема с подходом к интерфейсу замены "Друг" заключается в том, что объект предоставляет интерфейс, это означает, что любой может преобразовать объект в этот интерфейс и иметь доступ к методам! Привязка интерфейса к внутреннему, защищенному или частному не работает, как если бы это сработало, вы можете просто указать рассматриваемый метод! Подумайте о матери и ребенке в торговом центре. Публика - это каждый в мире. Внутренний - это просто люди в торговом центре. Частным остается только мать или ребенок. «Друг» - это что-то среднее между матерью и дочерью.

Mark A. Donohoe 07.07.2011 22:01

Вот один из них: stackoverflow.com/questions/5921612/… Поскольку я вырос в C++, отсутствие друзей сводит меня с ума почти ежедневно. За несколько лет работы с C# я сделал буквально сотни методов общедоступными, вместо этого они могли быть приватными и более безопасными, и все из-за отсутствия друзей. Лично я считаю, что друг - это самое важное, что нужно добавить в .NET.

kaalus 13.11.2012 03:35

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

GrandOpener 03.10.2015 23:17
Ответ принят как подходящий

Наличие друзей в программировании более или менее считается «грязным», и им легко злоупотреблять. Это нарушает отношения между классами и подрывает некоторые фундаментальные атрибуты объектно-ориентированного языка.

При этом это хорошая функция, и я сам много раз использовал ее на C++; и хотел бы использовать его и в C#. Но я уверен, что из-за "чистой" OOness C# (по сравнению с псевдо OOness C++) MS решила, что, поскольку у Java нет ключевого слова друга, C# тоже не должен (шутка;))

Если серьезно: внутреннее не так хорошо, как друг, но оно выполняет свою работу. Помните, что редко вы будете распространять свой код сторонним разработчикам не через DLL; так что до тех пор, пока вы и ваша команда знаете о внутренних классах и их использовании, все будет в порядке.

РЕДАКТИРОВАТЬ Позвольте мне пояснить, как ключевое слово friend подрывает ООП.

Частные и защищенные переменные и методы, пожалуй, одна из самых важных частей ООП. Идея о том, что объекты могут содержать данные или логику, которые могут использовать только они, позволяет вам писать свою реализацию функциональности независимо от вашей среды - и что ваша среда не может изменять информацию о состоянии, для обработки которой она не подходит. Используя friend, вы объединяете реализации двух классов вместе - что намного хуже, чем если бы вы просто объединили их интерфейс.

Таким образом, похоже, что причина, по которой ключевое слово «internal» находится в C#, а «friend» - нет, заключается в том, что внутреннее ключевое слово более явное, чем friend. Я думаю, как только вы видите внутренние, вы сразу понимаете, что другой класс может получить к ним доступ.

Ash 15.10.2008 07:47

Какие фундаментальные атрибуты подорваны? Какие отношения разорваны? Я хочу понять, о чем вы говорите. Заранее спасибо!

Esteban Araya 15.10.2008 08:02

Я не думаю, что друг был оставлен без внимания из-за "чистоты OO". Я думаю, это более вероятно, потому что C# переводится в IL при компиляции, и доступны другие типы, которые вы, возможно, захотите подружить. Точно так же обобщения C++ полностью выполняются во время компиляции, а C# - нет.

Stewart Johnson 15.10.2008 09:41

«Внутренний» C# на самом деле вредит инкапсуляции гораздо больше, чем «друг» C++. При «дружбе» хозяин прямо дает разрешение кому хочет. «Внутренний» - это открытое понятие.

Nemanja Trifunovic 15.10.2008 21:19

Андерса следует хвалить за те особенности, которые он упустил, а не за те, которые он включил.

mmx 16.04.2009 16:26

Вы по-прежнему можете использовать "friend", но вы можете применять его к сборкам, вместо того, чтобы позволить различным библиотекам совместно использовать внутренние классы.

Quibblesome 23.10.2009 17:06

Я не согласен с тем, что внутренняя часть C# мешает инкапсуляции. На мой взгляд, наоборот. Вы можете использовать его для создания инкапсулированной экосистемы в сборке. Некоторые объекты могут иметь общие внутренние типы в этой экосистеме, которые не доступны для остальной части приложения. Я использую приемы сборки «друга» для создания сборок модульных тестов для этих объектов.

Quibblesome 23.10.2009 17:11

В этом нет смысла. friend не позволяет произвольным классам или функциям обращаться к закрытым членам. Это позволяет определенным функциям или классам, которые были помечены как друзья, делать это. Класс, владеющий закрытым членом, по-прежнему контролирует доступ к нему на 100%. С таким же успехом можно утверждать, что методы public member. Оба гарантируют, что методы, специально перечисленные в классе, получают доступ к закрытым членам класса.

jalf 20.01.2010 08:16

Нет, этот ответ фактически неверен, и другие темы на SO уже объясняют, почему, например. stackoverflow.com/questions/17434/…

Konrad Rudolph 21.01.2010 15:13

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

John K 16.02.2010 15:17

@ Неманья Трифунович: «Внутренний» - это открытая концепция ». Не совсем. Поскольку вы, программист, контролируете, какие классы находятся в ваших сборках, вы неявно контролируете, какие классы могут получить к нему доступ. Однако в любом случае давать классам прямой доступ к вашим материалам - плохая идея, поэтому .NET представила синтаксис свойств.

Powerlord 17.02.2010 17:21

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

kaalus 20.10.2011 21:14

Я думаю совсем наоборот. Если сборка имеет 50 классов, если 3 из этих классов используют какой-либо служебный класс, то сегодня ваш единственный вариант - пометить этот служебный класс как «внутренний». При этом вы не только делаете его доступным для использования 3 классам, но и остальным классам в сборке. Что, если бы все, что вы хотели сделать, - это предоставить более детализированный доступ, чтобы только три рассматриваемых класса имели доступ к служебному классу. Фактически это делает его более объектно-ориентированным, поскольку улучшает инкапсуляцию.

zumalifeguard 30.10.2011 05:21

«Частные и защищенные переменные и методы, возможно, являются одной из наиболее важных частей ООП. Идея о том, что объекты могут содержать данные или логику, которые могут использовать только они, позволяет вам написать свою реализацию функциональности независимо от вашей среды - и что ваша среда не может изменить информацию о состоянии, для обработки которой он не подходит ". - это не аргумент, почему это подрывает ООП. Python вообще не нуждается в конфиденциальности: скорее, он зависит от принятой схемы именования, чтобы описать, к чему можно легко получить доступ, а к чему нет. Тем не менее, это во многом язык ООП.

paul23 06.03.2017 02:38

Не уверен, насколько серьезно вы шутите о C++ "псевдо OO" и C# "чистый OO", но что наверняка, так это то, что объектно-ориентированность никоим образом не подразумевает классы, особенно функции-члены нет, и супер крайности, особенно не модификаторы доступа, такие как private или public. Классы и члены с ограниченным доступом - это лишь один из способов реализации ООП. Классическое, связанное чтение на C++, касающееся членов и нечленов: Как функции, не являющиеся членами, улучшают инкапсуляцию

Sebastian Mach 30.10.2017 19:45

«редко вы будете распространять свой код сторонним разработчикам, не используя DLL» Насколько верно это остается десять лет спустя с появлением библиотек, распространяемых как бесплатное программное обеспечение и поддерживаемых на Microsoft GitHub?

Damian Yerrick 03.07.2018 17:10

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

AustinWBryan 24.07.2018 13:55

Я думаю, что любому программисту на C++ полезно иметь в виду, что когда два класса становятся друзьями, они больше не являются двумя отдельными классами, они стали одним классом, реализованным из двух частей (хотя каждая часть может существовать без Другие). Это почти то же самое, что и «частичные» классы в C#.

John Thoits 06.02.2021 22:46

Если вы работаете с C++ и обнаруживаете себя с помощью ключевого слова friend, это очень сильный признак того, что у вас есть проблема с дизайном, потому что какого черта классу нужен доступ к закрытым членам другого класса ??

Я согласен. Мне никогда не нужно ключевое слово друга. Обычно он используется для решения сложных проблем обслуживания.

Edouard A. 19.01.2009 19:29

Перегрузка оператора, кто-нибудь ??

We Are All Monica 22.09.2009 05:27

Сколько раз вы находили хороший случай серьезной перегрузки оператора?

bashmohandes 01.10.2009 02:17

Я обычно перегружаю operator (), operator << и некоторые другие.

Max Lybbert 22.01.2010 13:04

Я приведу вам очень простой случай, который полностью развенчивает ваш комментарий о «проблеме дизайна». Родитель-ребенок. Родитель владеет своими детьми. Дочерний элемент в родительской коллекции «Дети» принадлежит этому родителю. Установщик свойства Parent для Child не должен устанавливаться кем-либо. Его нужно назначать тогда и только тогда, когда дочерний элемент добавляется в родительскую коллекцию Children. tЕсли он публичный, любой может его изменить. Внутренний: любой участник этой сборки. Частный? Ни один. Никто. Сделав сеттер другом для родителя, вы избавитесь от этих случаев и по-прежнему сохраните ваши классы отдельными, но тесно связанными. Ура !!

Mark A. Donohoe 07.07.2011 21:54

Таких случаев очень много, например, самый базовый шаблон менеджер / управляемый. Есть еще один вопрос по SO, и никто не придумал, как это сделать без друга. Не уверен, что заставляет людей думать, что одного класса «всегда хватит». Иногда более одного класса должны работать вместе.

kaalus 20.10.2011 21:17

Ситуация, описанная «Маркелем», - прекрасный пример потребности в друге. Это будет единственный выбор, если у родителя есть список дочерних элементов, список должен быть в некотором порядке и каждый дочерний элемент должен иметь соответствующий индекс списка. Тогда очевидно, что родитель должен делать все с этим ребенком.

prabhakaran 29.05.2012 18:21

Для информации, еще одна связанная, но не совсем такая же вещь в .NET - это [InternalsVisibleTo], который позволяет сборке назначать другую сборку (например, сборку модульного теста), которая (по сути) имеет «внутренний» доступ к типам / членам. в оригинальной сборке.

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

SwissCoder 09.10.2012 15:22

На самом деле это не проблема C#. Это фундаментальное ограничение в IL. C# ограничен этим, как и любой другой язык .Net, который стремится быть проверяемым. Это ограничение также включает управляемые классы, определенные в C++ / CLI (Раздел спецификаций 20.5).

При этом я думаю, что у Нельсона есть хорошее объяснение того, почему это плохо.

-1. friend - неплохая вещь; напротив, он намного лучше, чем internal. А объяснение Нельсона плохое и неверное.

Nawaz 27.02.2012 09:46

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

Смотрите сообщениеКонрад Рудольф в другом потоке или, если вы предпочитаете, смотрите соответствующая запись в C++ FAQ.

... и соответствующая запись в FQA: yosefk.com/c++fqa/friend.html#fqa-14.2

Josh Lee 16.12.2009 15:16

+1 за "Использование друга - это не нарушение инкапсуляции, а, наоборот, обеспечение ее соблюдения."

Nawaz 27.02.2012 09:47

Думаю, дело в семантическом мнении; и, возможно, связаны с конкретной рассматриваемой ситуацией. Создание класса friend дает ему доступ ко ВСЕМ вашим частным членам, даже если вам нужно позволить ему установить только одного из них. Существует веский аргумент в пользу того, что предоставление полей другому классу, который в них не нуждается, считается «нарушением инкапсуляции». В C++ есть места, где это необходимо (операторы перегрузки), но есть компромисс инкапсуляции, который необходимо тщательно рассмотреть. Похоже, разработчики C# посчитали, что компромисс того не стоит.

GrandOpener 03.10.2015 23:39

Инкапсуляция - это обеспечение инвариантов. Когда мы определяем класс как друг для другого, мы явно говорим, что эти два класса сильно связаны в отношении управления инвариантами первого. Лучше, когда класс становится другом, чем выставлять все атрибуты напрямую (как общедоступное поле) или через сеттеры.

Luc Hermitte 04.10.2015 00:35

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

Hatchling 28.02.2017 23:55

Friend чрезвычайно полезен при написании модульного теста.

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

Я нашел очень полезную и чистую идиому, когда у меня есть фабричные классы, что делает их друзьями создаваемых ими элементов, имеющих защищенный конструктор. В частности, это было тогда, когда у меня была одна фабрика, отвечающая за создание соответствующих объектов рендеринга для объектов составителя отчетов, рендеринг в заданной среде. В этом случае у вас есть единая точка знаний о взаимосвязи между классами составителей отчетов (такими как блоки изображений, полосы макета, заголовки страниц и т. д.) И соответствующие им объекты визуализации.

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

Edward Robertson 16.09.2011 21:48

В C# отсутствует ключевое слово "friend" по той же причине, по которой отсутствует детерминированное уничтожение. Изменение условностей заставляет людей чувствовать себя умными, как будто их новые пути лучше чужих старых. Все дело в гордости.

Сказать, что «классы друзей - это плохо», так же недальновидно, как и другие неквалифицированные утверждения, такие как «не используйте gotos» или «Linux лучше, чем Windows».

Ключевое слово "friend" в сочетании с прокси-классом - отличный способ предоставить только части определенный класса конкретному другому классу (классам). Прокси-класс может выступать в качестве надежного барьера против всех других классов. «public» не допускает такого таргетинга, а использование «protected» для получения эффекта с наследованием неудобно, если действительно нет концептуальных отношений «есть».

В C# отсутствует детерминированное разрушение, потому что он медленнее, чем сборка мусора. Детерминированное разрушение требует времени для каждого созданного объекта, тогда как сборка мусора требует времени только для текущих живых объектов. В большинстве случаев это быстрее, и чем больше недолговечных объектов в системе, тем относительно быстрее происходит сборка мусора.

Edward Robertson 16.09.2011 21:51

@Edward Robertson Детерминированное разрушение не требует времени, если не определен деструктор. Но в этом случае сборка мусора намного хуже, потому что тогда вам нужно использовать финализатор, который работает медленнее и недетерминирован.

kaalus 20.10.2011 21:19

... и память - не единственный вид ресурса. Люди часто действуют так, как будто это правда.

cubuspl42 24.08.2015 17:15

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

Если вы не работаете над приложением Silverlight.

kaalus 20.10.2011 21:21

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

Это для обеспечения соблюдения парадигмы инкапсуляции? так что вам нужно написать методы доступа и что теперь? как вы должны запретить всем (кроме методов класса B) вызывать эти методы? вы не можете, потому что вы тоже не можете контролировать это из-за отсутствия «друга».

Нет идеального языка программирования. C# - один из лучших языков, которые я видел, но глупые оправдания отсутствующих функций никому не помогают. В C++ мне не хватает простой системы событий / делегатов, отражения (+ автоматическая де / сериализация) и foreach, но в C# я скучаю по перегрузке операторов (да, продолжайте говорить мне, что вам это не нужно), параметрам по умолчанию, константе это невозможно обойти, множественное наследование (да, продолжайте говорить мне, что оно вам не нужно, и интерфейсы были достаточной заменой) и возможность принять решение об удалении экземпляра из памяти (нет, это не так уж плохо, если вы не являетесь ремесленник)

В C# вы можете перегрузить большинство операторов. Вы не можете перегрузить операторы присваивания, но, с другой стороны, вы можете переопределить || / &&, сохраняя при этом их короткое замыкание. Параметры по умолчанию были добавлены в C# 4.

CodesInChaos 20.11.2011 14:11

С friend разработчик C++ имеет точный контроль над тем, кому доступны закрытые * члены. Но он вынужден разоблачить каждого из частных участников.

С internal разработчик C# имеет точный контроль над набором закрытых членов, которые он раскрывает. Очевидно, он может раскрыть только одного закрытого члена. Но он будет доступен для всех классов в сборке.

Как правило, разработчик желает предоставить только несколько частных методов избранным нескольким другим классам. Например, в шаблоне фабрики классов может быть желательно, чтобы экземпляр класса C1 создавался только фабрикой классов CF1. Следовательно, класс C1 может иметь защищенный конструктор и фабрику дружественных классов CF1.

Как видите, у нас есть 2 измерения, по которым может быть нарушена инкапсуляция. friend пробивает его по одному измерению, internal - по другому. Какой из них является более серьезным нарушением концепции инкапсуляции? Тяжело сказать. Но было бы неплохо иметь в наличии и friend, и internal. Кроме того, хорошим дополнением к этим двум будет третий тип ключевого слова, который будет использоваться по отдельности (например, internal) и указывает целевой класс (например, friend).

* Для краткости я буду использовать «частный» вместо «частный и / или защищенный».

- Ник

Раньше я регулярно использовал friend и не думаю, что это нарушение ООП или признак какой-либо ошибки в дизайне. Есть несколько мест, где это наиболее эффективное средство достижения нужного результата с наименьшим количеством кода.

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

Просто глупо заявлять, что это нарушение ООП для класса протокола / "создателя", чтобы иметь непосредственный доступ ко всем созданным классам - класс-создатель должен был изменять каждый бит данных на пути вверх. Что я считаю наиболее важным, так это свести к минимуму все лишние строки кода BS, к которым обычно приводит модель «ООП ради ООП». Дополнительные спагетти просто порождают больше ошибок.

Знают ли люди, что вы можете применить ключевое слово internal на уровне атрибута, свойства и метода? Это касается не только объявления класса верхнего уровня (хотя большинство примеров, кажется, это демонстрируют).

Если у вас есть класс C++, который использует ключевое слово friend, и вы хотите имитировать это в классе C#: 1. объявить класс C# общедоступным 2. объявить все атрибуты / свойства / методы, которые защищены в C++ и, следовательно, доступны друзьям, как внутренние в C#. 3. создать свойства только для чтения для общего доступа ко всем внутренним атрибутам и свойствам

Я согласен, что это не на 100% то же самое, что и friend, и модульный тест - очень ценный пример необходимости чего-то вроде друга (как и код журналирования анализатора протокола). Однако internal обеспечивает доступ к классам, которые вы хотите получить, а [InternalVisibleTo ()] обрабатывает все остальное - похоже, он был создан специально для модульного тестирования.

Что касается друга «быть лучше, потому что вы можете явно контролировать, какие классы имеют доступ» - что, черт возьми, группа подозрительных злых классов делает в одной и той же сборке в первую очередь? Разбиение сборок на разделы!

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

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

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

Я прочитал много умных комментариев о ключевом слове «друг», и я согласен, что это полезная вещь, но я думаю, что какое «внутреннее» ключевое слово менее полезно, и оба они все еще плохи для чистого объектно-ориентированного программирования.

Что мы имеем? (говоря о «друге» я также говорю о «внутреннем»)

  • использование "друга" делает код менее чистым в отношении oo?
  • да;

  • не использование "друга" делает код лучше?

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

Использование друга создает некоторые локальные проблемы, а неиспользование его создает проблемы для пользователей библиотеки кода.

общее хорошее решение для языка программирования я вижу так:

// c++ style
class Foo {
  public_for Bar:
    void addBar(Bar *bar) { }
  public:
  private:
  protected:
};

// c#
class Foo {
    public_for Bar void addBar(Bar bar) { }
}

Что вы думаете об этом? Я думаю, что это наиболее распространенное и чисто объектно-ориентированное решение. Вы можете открыть доступ к любому выбранному вами методу для любого класса.

Я не стражник ООП, но похоже, что он решает все претензии к другу и внутреннему. Но я бы предложил использовать атрибут, чтобы избежать расширения синтаксиса C#: [PublicFor Bar] void addBar (Bar bar) {} ... Не то чтобы я уверен, что это имеет смысл; Я не знаю, как работают атрибуты и могут ли они возиться с доступностью (они делают много других «волшебных» вещей).

Grault 15.03.2013 12:28

Дружбу можно смоделировать, разделив интерфейсы и реализации. Идея такая: «Требовать конкретный экземпляр, но ограничить доступ для строительства этого экземпляра».

Например

interface IFriend { }

class Friend : IFriend
{
    public static IFriend New() { return new Friend(); }
    private Friend() { }

    private void CallTheBody() 
    {  
        var body = new Body();
        body.ItsMeYourFriend(this);
    }
}

class Body
{ 
    public void ItsMeYourFriend(Friend onlyAccess) { }
}

Несмотря на то, что ItsMeYourFriend() является общедоступным, только класс Friend может получить к нему доступ, поскольку никто другой не может получить конкретный экземпляр класса Friend. У него есть частный конструктор, а фабричный метод New() возвращает интерфейс.

Подробности смотрите в моей статье Друзья и участники внутреннего интерфейса бесплатно с кодированием интерфейсов.

К сожалению, дружбу невозможно смоделировать. См. Этот вопрос: stackoverflow.com/questions/5921612/…

kaalus 13.11.2012 03:46

B.s.d.

Было заявлено, что друзья вредит чистому OOness. С чем я согласен.

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

Я думаю, что дружба должна быть добавлена ​​в методологию объектно-ориентированного программирования, но не совсем так, как в C++. Я хотел бы иметь некоторые поля / методы, к которым мой класс друзей может получить доступ, но я НЕ хочу, чтобы они получали доступ ко ВСЕМ моим полям / методам. Как и в реальной жизни, я позволял своим друзьям иметь доступ к моему личному холодильнику, но не позволял им получать доступ к моему банковскому счету.

Это можно реализовать следующим образом

    class C1
    {
        private void MyMethod(double x, int i)
        {
            // some code
        }
        // the friend class would be able to call myMethod
        public void MyMethod(FriendClass F, double x, int i)
        {
            this.MyMethod(x, i);
        }
        //my friend class wouldn't have access to this method 
        private void MyVeryPrivateMethod(string s)
        {
            // some code
        }
    }
    class FriendClass
    {
        public void SomeMethod()
        {
            C1 c = new C1();
            c.MyMethod(this, 5.5, 3);
        }
    }

Это, конечно, вызовет предупреждение компилятора и повредит intellisense. Но он сделает свою работу.

Кстати, я думаю, что уверенный в себе программист должен выполнять модуль тестирования, не обращаясь к закрытым членам. это выходит за рамки возможностей, но попробуйте почитать про TDD. однако, если вы все еще хотите это сделать (имея друзей на C++), попробуйте что-нибудь вроде

#if UNIT_TESTING
        public
#else
        private
#endif
            double x;

поэтому вы пишете весь свой код без определения UNIT_TESTING, и когда вы хотите выполнить модульное тестирование, вы добавляете #define UNIT_TESTING в первую строку файла (и пишете весь код, который выполняет модульное тестирование, под #if UNIT_TESTING). С этим нужно обращаться осторожно.

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

Этот параметр FriendClass не служит для контроля доступа: вы можете вызывать c.MyMethod((FriendClass) null, 5.5, 3) из любого класса.

Jesse McGrew 29.09.2012 03:25

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

У меня есть страница ASP.NET, которая наследует мою собственную базовую страницу, которая, в свою очередь, наследует System.Web.UI.Page. На этой странице у меня есть код, который обрабатывает отчеты об ошибках конечного пользователя для приложения в защищенном методе.

ReportError("Uh Oh!");

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

MyBasePage bp = Page as MyBasePage;
bp.ReportError("Uh Oh");

Этого не может быть, если метод ReportError защищен. Я могу сделать его внутренним, но он доступен для любого кода сборки. Я просто хочу, чтобы он был доступен для элементов пользовательского интерфейса, которые являются частью текущей страницы (включая дочерние элементы управления). В частности, я хочу, чтобы мой базовый класс управления определял точно такие же методы сообщения об ошибках и просто вызывал методы на базовой странице.

protected void ReportError(string str) {
    MyBasePage bp = Page as MyBasePage;
    bp.ReportError(str);
}

Я считаю, что что-то вроде друга может быть полезно и реализовано на языке, не делая язык менее «объектно-ориентированным», например, в виде атрибутов, так что у вас могут быть классы или методы, дружащие с определенными классами или методами, что позволяет разработчику предоставлять очень специфический доступ. Возможно что-то вроде ... (псевдокод)

[Friend(B)]
class A {

    AMethod() { }

    [Friend(C)]
    ACMethod() { }
}

class B {
    BMethod() { A.AMethod() }
}

class C {
    CMethod() { A.ACMethod() }
}

В случае с моим предыдущим примером, возможно, есть что-то вроде следующего (можно поспорить с семантикой, но я просто пытаюсь донести идею):

class BasePage {

    [Friend(BaseControl.ReportError(string)]
    protected void ReportError(string str) { }
}

class BaseControl {
    protected void ReportError(string str) {
        MyBasePage bp = Page as MyBasePage;
        bp.ReportError(str);
    }
}

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

Дружбу также можно смоделировать с помощью «агентов» - некоторых внутренних классов. Рассмотрим следующий пример:

public class A // Class that contains private members
{
  private class Accessor : B.BAgent // Implement accessor part of agent.
  {
    private A instance; // A instance for access to non-static members.
    static Accessor() 
    { // Init static accessors.
      B.BAgent.ABuilder = Builder;
      B.BAgent.PrivateStaticAccessor = StaticAccessor;
    }
    // Init non-static accessors.
    internal override void PrivateMethodAccessor() { instance.SomePrivateMethod(); }
    // Agent constructor for non-static members.
    internal Accessor(A instance) { this.instance = instance; }
    private static A Builder() { return new A(); }
    private static void StaticAccessor() { A.PrivateStatic(); }
  }
  public A(B friend) { B.Friendship(new A.Accessor(this)); }
  private A() { } // Private constructor that should be accessed only from B.
  private void SomePrivateMethod() { } // Private method that should be accessible from B.
  private static void PrivateStatic() { } // ... and static private method.
}
public class B
{
  // Agent for accessing A.
  internal abstract class BAgent
  {
    internal static Func<A> ABuilder; // Static members should be accessed only by delegates.
    internal static Action PrivateStaticAccessor;
    internal abstract void PrivateMethodAccessor(); // Non-static members may be accessed by delegates or by overrideable members.
  }
  internal static void Friendship(BAgent agent)
  {
    var a = BAgent.ABuilder(); // Access private constructor.
    BAgent.PrivateStaticAccessor(); // Access private static method.
    agent.PrivateMethodAccessor(); // Access private non-static member.
  }
}

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

Отвечу только на вопрос "Как".

Здесь так много ответов, однако я хотел бы предложить своего рода «шаблон проектирования» для достижения этой функции. Я буду использовать простой языковой механизм, который включает:

  • Интерфейсы
  • Вложенный класс

Например, у нас есть 2 основных класса: Студент и Университет. У студента есть средний балл, доступ к которому имеет только университет. Вот код:

public interface IStudentFriend
{
    Student Stu { get; set; }
    double GetGPS();
}

public class Student
{
    // this is private member that I expose to friend only
    double GPS { get; set; }
    public string Name { get; set; }

    PrivateData privateData;

    public Student(string name, double gps) => (GPS, Name, privateData) = (gps, name, new PrivateData(this);

    // No one can instantiate this class, but Student
    // Calling it is possible via the IStudentFriend interface
    class PrivateData : IStudentFriend
    {
        public Student Stu { get; set; }

        public PrivateData(Student stu) => Stu = stu;
        public double GetGPS() => Stu.GPS;
    }

    // This is how I "mark" who is Students "friend"
    public void RegisterFriend(University friend) => friend.Register(privateData);
}

public class University
{
    var studentsFriends = new List<IStudentFriend>();

    public void Register(IStudentFriend friendMethod) => studentsFriends.Add(friendMethod);

    public void PrintAllStudentsGPS()
    {
        foreach (var stu in studentsFriends)
            Console.WriteLine($"{stu.Stu.Name}: stu.GetGPS()");
    }
}

public static void Main(string[] args)
{
    var Technion = new University();
    var Alex     = new Student("Alex", 98);
    var Jo       = new Student("Jo", 91);

    Alex.RegisterFriend(Technion);
    Jo.RegisterFriend(Technion);
    Technion.PrintAllStudentsGPS();

    Console.ReadLine();
}

Фактически, C# дает возможность получить такое же поведение в чистом виде ООП без специальных слов - это частные интерфейсы.

Поскольку вопрос Что представляет собой эквивалент слова friend в C#? был помечен как дублирующий к этой статье, и там никто не предлагает действительно хорошей реализации - я покажу здесь ответы на оба вопроса.

Основная идея была взята отсюда: Что такое приватный интерфейс?

Скажем, нам нужен какой-то класс, который мог бы управлять экземплярами других классов и вызывать для них какие-то специальные методы. Мы не хотим давать возможность вызывать эти методы другим классам. Это в точности то же самое, что ключевое слово друга C++ делает в мире C++.

Я думаю, что хорошим примером на практике может быть шаблон Full State Machine, когда какой-либо контроллер обновляет объект текущего состояния и при необходимости переключается на другой объект состояния.

Ты мог бы:

  • Самый простой и худший способ сделать метод Update () общедоступным - надеюсь все понимают, почему это плохо.
  • Следующий способ - пометить его как внутренний. Достаточно хорошо, если вы положите классы в другую сборку, но даже тогда каждый класс в этой сборке мог вызвать каждый внутренний метод.
  • Используйте частный / защищенный интерфейс - и я пошел по этому пути.

Controller.cs

public class Controller
{
    private interface IState
    {
        void Update();
    }

    public class StateBase : IState
    {
        void IState.Update() {  }
    }

    public Controller()
    {
        //it's only way call Update is to cast obj to IState
        IState obj = new StateBase();
        obj.Update();
    }
}

Program.cs

class Program
{
    static void Main(string[] args)
    {
        //it's impossible to write Controller.IState p = new Controller.StateBase();
        //Controller.IState is hidden
        var p = new Controller.StateBase();
        //p.Update(); //is not accessible
    }
}

Ну что насчет наследования?

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

Controller.cs

public class Controller
{
    protected interface IState
    {
        void Update();
    }

    public class StateBase : IState
    {
        void IState.Update() { OnUpdate(); }
        protected virtual void OnUpdate()
        {
            Console.WriteLine("StateBase.OnUpdate()");
        }
    }

    public Controller()
    {
        IState obj = new PlayerIdleState();
        obj.Update();
    }
}

PlayerIdleState.cs

public class PlayerIdleState: Controller.StateBase
{
    protected override void OnUpdate()
    {
        base.OnUpdate();
        Console.WriteLine("PlayerIdleState.OnUpdate()");
    }
}

И, наконец, пример того, как проверить наследование броска контроллера класса: ControllerTest.cs

class ControllerTest: Controller
{
    public ControllerTest()
    {
        IState testObj = new PlayerIdleState();
        testObj.Update();
    }
}

Надеюсь, я рассмотрю все случаи и мой ответ был полезен.

Это отличный ответ, который требует больше голосов.

KthProg 19.04.2017 18:30

@max_cn Это действительно хорошее решение, но я не вижу способа заставить его работать с макетными фреймворками для модульного тестирования. Какие-либо предложения?

Steve Land 28.11.2018 18:46

Я немного запутался. Если бы я хотел, чтобы внешний класс мог вызывать Update в вашем первом примере (частный интерфейс), как бы мне изменить Controller, чтобы это произошло?

Anthony Monterrosa 28.02.2020 21:53

@Steve Land, вы можете использовать наследование, как было показано в ControllerTest.cs. Добавьте все необходимые общедоступные методы в производный тестовый класс, и вы получите доступ ко всему защищенному персоналу, имеющемуся в базовом классе.

max_cn 24.04.2020 16:35

@AnthonyMonterrosa, мы скрываем обновление от ВСЕХ внешних ресурсов, так почему вы хотите поделиться им с кем-то;)? Конечно, вы можете добавить какой-нибудь общедоступный метод для доступа к частным членам, но это будет как бэкдор для любых других классов. В любом случае, если вам это нужно для целей тестирования - используйте наследование, чтобы создать что-то вроде класса ControllerTest, чтобы делать там какие-либо тестовые случаи.

max_cn 24.04.2020 16:48

Это чрезвычайно многословный и запутанный способ добиться худшего эффекта по сравнению с тем, что предоставляет "друг" (можно по-прежнему приводить к интерфейсу и вызывать любые функции, которые захотят). Отсутствие друга в C# кусает меня почти каждый день в моей повседневной работе. Аргументы «чистого ООП» просто неверны: иногда два класса должны иметь частное разделяемое состояние. Друг обеспечивает это. «Чистое ООП» - нет.

kaalus 28.12.2020 20:07

Другие вопросы по теме