
Комментарии 24
Обычно под "Elvis operator" имеют в виду "?:", иногда "??" или "?.", поэтому по названию кажется, что будет что-нибудь типа "protected?"
В каком-то смысле модификатор и защищает, только не от NullReferenceException, а от нарушения бизнес-логики/контракта.
Если бы не было «Elvis оператора», то наверное и не подумал так назвать модификатор, а так дорога уже проложена, считайте традиция, да и для «маркетинга», имхо, хорошее название (в кавычках, потому как какой маркетинг при MIT лицензии).
На счет "?:" не встречал чтобы называли его "Elvis operator". Классический Элвис-олератор, как верно далее пишете, это "??" (также как и "?.", тоже относится к Элвис семейству операторов).
Это в Kotlin. В C# же (наш контекст) "?:" все же принято называть тернарным оператором (очень старый оператор еще до Элвис оператора):
и, как минимум, гугл не согласен называть его Элвис оператором в C#:
Тернарный оператор и элвис это для широкой аудитории одно и тоже. Потому что выглядит и ведет себя одинаково. А вы хотите присвоить устоявшуюся терминологию, и от того лезете сейчас в бутылку.
Пожалуйста, приведите мне к-л авторитетное руководство по C#, где тернарный оператор называли бы элвис оператором. может плохо ищу:
И, поверьте, не «лезу в бутылку», готов признавать свои ошибки (буду только благодарен если укажите на них, ставлю за них лайки, как тут ниже для chegeras и вам готов поставить), готов узнавать что-то новое. Просто пока не понимаю вас.
И опять замечу, что речь в статье о C#, не о Kotlin. В C# именно вот так ?: одной строкой вообще нет такого оператора.
Ваша позиция понятна, но ошибочна. То что в C# и dotnet оператор называется тернарным, вообще не исключает того что он же много лет неформально называется элвисом. Нельзя просто взять слово из устоявшейся лексикона и использовать в новом смысле, явно создавая путаницу.. просто потому что вам нравится Пресли.
Увы, авторитетных доказательств что именно в C# тернарный оператор называется еще и Элвис оператором вы не привели. Уважаю ваше мнение и право называть что-либо как вы хотите, но пока тот же гугл не разделяет его. Даже не минусую ваши посты (в отличии от вас).
В статье же речь не об операторе, а о модификаторе, так что какая путаница?
Потому как этот атрибут фактически и выполняет функцию модификатора доступа, конечно, нового модификатора не входящего в стандарт. Поэтому счел возможным назвать как модификатор.
У нас с вами дискуссии по названиям. Имхо, названия не столь важны как суть, предлагаю не продолжать их.
предлагаю не продолжать их
Ваше предложение отвергнуто.
В C# модификаторы доступа прибиты гвоздями к CLR, и как синтаксис доступа к классам их свойствам не отделимы от него.
счел возможным назвать как модификатор
Как удобно у вас получается, тут значит затребую доказательства, а тут "счел возможным". Вы "либо крестик снимите, либо трусы наденьте", а то как-то очень избирательно у вас всё.
Ну почему "прибиты гвоздями", вот новые появляются как тот же file модификатор. И, конечно, в CLR не лезу, кто же мне позволит? Ну назвал модификатором инструмент, который фактически выполняет функцию модификатора. Вы не согласны с названием, понял вас, уважаю ваше мнение (говорю, даже ни одного минуса не поставил вам в отличии от вас, после моих ответов). Смысл дальше спорить? (и не я его начинал, лишь вежливо отвечаю вам)
Странный спор у вас тут происходит, но ваша собственная ссылка различает elvis (a ?: b) и тернарный (a ? b : c) операторы. У них же даже арность разная, как они могут быть одним и тем же.
А если говорить про C#, у нас под elvis'ом всегда понимался null-conditional operator (?.), а тернарный всегда был тернарным.
https://en.wikipedia.org/wiki/Elvis_operator
In GNU C and C++ (that is: in C and C++ with GCC extensions), the second operand of the ternary operator is optional.[4] This has been the case since at least GCC 2.95.3 (March 2001), and seems to be the original Elvis operator.[5]
Я имел в виду именно ?: как бинарный оператор, а не тернарный, и не утверждал, что это в C#.
в этом примере куда-то делись деньги, у меня ушло 50, а другу пришло 20
это налог на дефолтную имплементацию в интерфейсах?
me: 100; friend: -10
me: 50; friend: 10
Если честно, выглядит как оверинжинириг для сумрачного гения. Код должен быть интуитивно понятен, а тут интуитивностью не пахнет, и очень напоминает мне маниакальное обмазывание декораторами в java.
Не знаю. Не хочу умалять ваш труд, но я тоже не вижу для себя применение этой штуки. Чем старше я становлюсь, тем больше ценю подходы потупее и попроще.
Надо подружить два класса - можно повесить internal и положить в отдельную сборку (хотелось бы в дотнете модулей поменьше, как в Rust, ну да ладно). Либо через explicit interface implementation, чтобы методы или свойства не отсвечивали в intellisense. Либо скрыть через EditorBrowsable(Never). Либо как в EF Core - положить в Internal namespace и написать анализатор. Влезть можно - но на свой страх и риск. В конце концов, знак "Осторожно! Злая собака" не призван запретить вам перелезать через забор, только лишний раз подумать ;)
Большой плюс подходов выше - они простые, используются много лет и всем знакомы. Если разработчик увидит EditorBrowsable(Never) - он поймёт что это такое и для чего.
Большой минус OnlyYou<T> - сложность её понимания превосходит пользу, которую она приносит. Имхо, конечно.
ПС. Поставил вам плюсик за готовность воспринимать критику. Эта статья читается намного проще.
Withkittens Спасибо за профессиональный пост!
Вы правы, есть разные решения, которые не умоляю. Да, и в Rust есть, что хотелось бы иметь и в C#.
Предлагаемый вариант, имхо, все же гибче, селективнее описанных вами подходов. В примерах в «Приложении» попытался показать применения. Возможно дело привычки (всегда кажется лучше и проще то к чему привык). Наверное пристрастен, только, имхо, просто поставить на мембере или классе/интерфейсе атрибут с вполне очевидным смыслом (Только Ты <T>), не так уж напряженно. Вот вы, как понял, профи и в Rust, вот там, имхо, действительно сложные вещи (как минимум за себя скажу — ломал мозги когда изучал).
На работе реорганизацию устроили ...
Собрали ВСЕХ с IT дипломами и озадачили развитием компании, в т.ч. направления программирования.
Также и тематикой озадачили указанной у данного автора.
Я пока только разбираюсь в вопросе, но полагаю методика интересная и заслуживает внимания.
Сразу скажу что публикации плюс поставил, т.к. заинтересован в развитии данной темы на Хабре.
Не так много публикаций подробных, подобной этой нашел. Этих данных в прямой выдаче в Интернете немного, IMHO
P.S. Озадачили на работе именно тематикой разбираемой (довольно подробно) в данном посте.
Стараюсь отслеживать, искать новые публикации/материалы.
Лично мне не хватает опыта, и данная статья очень кстати.
Доброе...
Начну с середины
Если мембер где-то используется, то эта связь так и так есть. Декларация разрешающая ее тут лишь «документирует» этот факт.
Это не так. И, имхо, это достаточно очевидно. Например, достаточно "представить", что "аттрактор" и "друг" это типы из разных сборок. "Лишняя" связь тут же станет очевидной. Нет?
Представить в кавычках, т.к. сильно не уверен, что "оно" умеет в "такое". Код не смотрел. Но без "такого" ценность (и так не очевидная) этой штуки прям сильно уменьшается, имхо.
Через интерфейс
Вот как раз "через интерфейс" исходная задача решается "с точностью до наоборот". В том смысле, что исходная "сущность" - мутабельная, а через реализацию интерфейса предоставляется readonly проекция. Нет? При таком подходе, "грануляция" доступа - пока отбросив её ценность - органично "схлопывается" до агрегатов. Оно может быть и выглядит громоздко на синтетике, но смотрится вполне себе нормально и логично (отбросив, опять же, всё остальное, что можно "предъявить" такому агрегату).
В C++ весь контроль над друзьями находится на стороне класса ..., что по идее более правильно.
Как бы это уже всё давно разжевано не один раз. "Это" - как раз - не правильно. Механизм "дружбы" в плюсах - это следствие, а не причина, так сказать. И если речь о "дружбе" на уровне классов, то реальной ценности там нет. Если мне не изменяет мой склероз, даже Страуструп предлагал смотреть на "дружбу" исключительно с позиции friend function (например, при реализации операторов).
В отличии от плюсов, не можем сделать другом только для конкретного метода другого класса.
Глубокое имхо... ценность такой "гранулярности" - есть следствие "кривоватого" дизайна. Насколько я понимаю, оно может быть хоть сколько-то ценно при работе с - скажем так - перегруженным агрегатом, в качестве аттрактора. Но даже при этом - мы живём в реальном мире, и не отрицаем этого - я бы сильно подумал над тем, нужно ли мне плодить доп. связность, практически, на ровном месте. Есть более "ровные" способы обеспечить такого рода разграничения. Особенно, если речь исключительно о конкретной сборке.
То что приватно и должно оставаться приватным. Тем более что всякого приватного у класса может быть очень много, и выставлять все это хозяйство на показ (как в плюсах) тот еще «эксгибиционизм».
?! Ну т.е. все "минусы" упомянутые вами ранее - это на самом деле не "минусы"? Так? И мы - внезапно - осознаем что из "C++ дружбы" хоть сколько-то ценно только friend function? Или как?
Пусть даже если так. Но в чём ценность такой "дружбы" для C#? "Дать доступ" же не является само целью? Надеюсь. В "плюсах" для этой "штуки" есть вполне конкретная область - операторы - в которой "оно" прям ценно. В том смысле, что другими механиками языка это "не лечится".
А в C# где вы видите эту "область ценности"? Вопрос вызван тем, что - выглядит так - начали мы с одного, а пришли к другому :-) Т.е. решали одну задачу, а решение (которое получилось) - это решение какой-то совсем другой задачи. Вот и интересно - какой? Там у вас есть некоторые "рассуждения на тему". Но это именно что "рассуждения не тему". Можете ли вы переформулировать их в терминах той задачи, которая сформулирована в начале статьи?
Следующий тип случаев с иерархией, реализован так ...
Хм... имхо, это "немножко" не логично. Логично, имхо, требовать для этого случая явной реализации интерфейса. Во избежание, так сказать. Ну и - если уж всё это навеяно "C++ дружбой" - не стоит забывать про "три не". Они не просто так были сформулированы. А из рассуждений про "как должно быть правильно", складывается стойкое ощущение, что про одно из не вы всё время забываете :-(
А предлагается инструмент лишенный их недостатков — новый модификатор доступа, дополняющий стандартную коллекцию модификаторов.
Ну это как сказать :-) Если оно - как я подозреваю (хочу ошибаться) - работает только в рамках конкретной сборки... лично мне вот вообще не понятна ценность этого "инструмента". И даже если представить, что это такой "заход" в т.н. sealed hierarchy - что могло бы иметь ценность, применительно к C#... оно - всё равно - выглядит сильно "странновато" и "не ровно".
Модификаторы позволяют делать так, что группа классов, не только семантически (в голове у разработчика), но теперь и синтаксически образует целостный концепт, без «торчащих ниток» за которые могут дергать кто угодно.
Когда вы говорите про "торчащие нитки", вы вообще о чём? Эти "нитки" они из сборки чтоль "торчат"? Или откуда? Складывается ощущение что вам кто-то строго запретил пользоваться internal (и его производными) уровнем доступа и использовать neasted иерархии. Это, кстати, относится и к приведенным вами "примерам".
Без более строго сформулированного "зачем" - лично у меня - статья оставляет сильное "хлебное" послевкусие :-)
Спасибо!
Прежде всего отмечу, что, имхо, польза может быть особенно в проектах с большим, сложным внутреннем API, где важно строгое соблюдения контрактов/бизнес-логики.
Сборок может быть сколько надо, нет ограничений, главное чтобы аналайзер был ко всем ним подключен, или подключен хотя бы пакет с атрибутам, если в какой-то сборке функции аналайзера не нужны, но атрибуты используются (если все в одном сольюшене, то предлагался Directory.Build.props).
Например, достаточно "представить", что "аттрактор" и "друг" это типы из разных сборок. "Лишняя" связь тут же станет очевидной. Нет?
В разных они сборках или нет, в любом случае, они знают друг о друге (иначе как декларацию декларировать?). Знают и используют (уже имеем связь), если же не используют, то писал о варининге и TODO (или, наоборот, можно сказать, UNDO индикаторе, если оказался просчет в начальном проектировании).
Вот как раз "через интерфейс" исходная задача решается "с точностью до наоборот". В том смысле, что исходная "сущность" - мутабельная, а через реализацию интерфейса предоставляется readonly проекция. Нет? При таком подходе, "грануляция" доступа - пока отбросив её ценность - органично "схлопывается" до агрегатов. Оно может быть и выглядит громоздко на синтетике, но смотрится вполне себе нормально и логично (отбросив, опять же, всё остальное, что можно "предъявить" такому агрегату).
Исходная сущность (класс Me) внешне как раз не мутабельная (readonly), а
"через интерфейс" предоставляется строго избирательная "проекция" с доступом к внешне скрытым или/и readonly мемберам.
Простите, про агрегацию, плохо вас понял. Имеете ввиду inner/nested классы (когда inner класс может получать доступ к закрытым мемберам outer класса)?
Про френды в C++ уже который раз пожалел, что вообще упомянул, они лишь начальная ассоциация, и, смотрю, каждый раз, «вызывают на себя огонь». Вот их можем отбросить, они лишь путают в понимании.
(а правило "три не" в плюсах, хорошее правило, если бы еще все его знали и использовали, это ведь только благое пожелание, а не запреты на уровне синтаксиса языка).
Помимо основного тела статьи, в «Приложении» сделал много примеров задач с возможным применением, обоснованием зачем. Думал достаточно (можно попробовать их решать другими методами, не спорю, и тогда уж сравнить что будет проще/лаконичнее...).
Когда вы говорите про "торчащие нитки", вы вообще о чём?
Про «торчащие нитки» (образное выражение), за которые могут дергать (т.е. использовать эти нитки-мемберы/классы: вызывать методы, сеттить свойства...) все (ну почти все с учетом более глобальных модификаторов) в том числе и те кому не желательно согласно контракту (это иллюстрировал в примерах).
Складывается ощущение что вам кто-то строго запретил пользоваться internal (и его производными) уровнем доступа и использовать neasted иерархии.
internal и nested не дает той гибкости и селективности, как данный модификатор.
internal работает «по площадям».
nested не всегда возможен (а если нужен как nested сразу в нескольких классах? а если нужен «взаимный» nested?) и удобен, и он получает доступ сразу ко всем приватным мемберам outer класса (фактически как в плюсах в варианте не function friend), что не есть хорошо.
Про оценку ценности тут, конечно, пусть каждый сам приценяется, насколько ему интересен данный функционал (сугубо лично мне интересен).
Прежде всего отмечу, что, имхо, польза может быть особенно в проектах с большим, сложным внутреннем API, где важно строгое соблюдения контрактов/бизнес-логики.
Я это уже читал, но так и не понял в чём конкретно эта "польза". Другими словами, я не понимаю, каким образом дополнительная связность (хоть её наличие вы и отрицаете) может быть полезна. Особенно, в "проектах с большим, сложным внутреннем API".
Сборок может быть сколько надо, нет ограничений ...
Ну я таки глянул код. Позвольте усомниться в этом вашем утверждении. Если аттрактор и друг это типы из разных сборок, то возникает циклическая зависимость между сборками. Причина - та самая "лишняя" связность, наличие которой вы отрицаете, но которой, тем не менее, нет в канонических вариантах решения.
Собственно, это (наличие циклической зависимости) было очевидно и без кода... но я надеялся, что у вас там она разрешается, например, через генерацию отдельной сборки. Я ошибался.
В разных они сборках или нет, в любом случае, они знают друг о друге ...
При традиционных подходах к - та часть, что в вашем варианте становится аттрактором ничего не знает о "дружественной" части.
Исходная сущность (класс Me) внешне как раз не мутабельная (readonly), а
"через интерфейс" предоставляется строго избирательная "проекция" с доступом к внешне скрытым или/и readonly мемберам.
Я это понял :-) Моя реплика была про то, что каноническое решение выглядит с точностью до наоборот. Если хотите, то вопрос в том, почему у вас не так? Почему за основу взят - так сказать - "вывернутый" вариант?
... а правило "три не" в плюсах, хорошее правило ...
Как я понимаю, мы о разных "трех не" говорим. Речь была о том, что "дружба" не наследуется. Не просто так. Очевидно, что - как минимум - в отношении к интерфейсам у вас "всё не так". Я, собственно, поэтому и упомянул явную реализацию.
Помимо основного тела статьи, в «Приложении» сделал много примеров задач с возможным применением, обоснованием зачем.
К сожалению, там нет "обоснования зачем". Там есть "смотри как могу". Любой из рассмотренных примеров замечательно решается без "этого". Собственно, в большинстве случаев, вы сами же об этом и пишите. Поэтому и был мой вопрос про "область ценности".
Про «торчащие нитки» ...
Опять же... если кто-то, что-то "видит" - он имеет полное право за это "дергать". И - скажем так - "прикладная задача проектирования" сводится к "надо сделать так, чтобы каждый видел только то, что ему можно дергать". А вы - буквально - говорите "а давайте на это всё забьем" :-)
internal и nested не дает той гибкости и селективности, как данный модификатор.
Вот ваш пример:
class Me
{
[OnlyYou<IFriend>(..)]
public void SomeMethod() { }
}
interface IFriend
{
void UseMe(Me me);
}
Объясните мне, пожалуйста, чем "это" - в принципе - отличается от?
Мой пример:
class Me
{
private void SomeMethod() { }
interface IFriend
{
void UseMe(Me me) => me.SomeMethod();
}
}
Мне вот видится, что - принципиально - ничем. И, имхо, второй вариант "чище", как минимум, как раз из-за того, что ничего не "торчит". Т.е. с мой точки зрения, всё что вы пытаетесь делать в такого рода вариантах - это заменить вполне традиционную технику т.н. mixin типов, на непонятно что :-(
internal работает «по площадям»
Зато для него есть, например, InternalsVisibleToAttribute, который позволяет вытащить "потроха" в отдельную сборку, и уже там сделать "торчать красиво" :-) В смысле, чтоб "торчало" только то, что нужно. И не приводит, при этом, к циклическим зависимостям.
nested не всегда возможен (а если нужен как nested сразу в нескольких классах? а если нужен «взаимный» nested?) и удобен, и он получает доступ сразу ко всем приватным мемберам outer класса (фактически как в плюсах в варианте не function friend), что не есть хорошо.
Ну вы чего?! Через neasted объявляются агрегаты (сильно упрощая - интерфейсы), которые потом используются для "сборки" того, что вам нужно где-то "во вне". Т.е. если вам прям так нужен "слуга двух господ" - вы вполне можете его получить без особых приседаний. И даже в другой сборке, если надо.
Если говорить за "вообще", то приемов работы с tight coupling - мы знаем не мало. В том смысле, что мы знаем "как есть этих слонов". Проблема - ту которую я вижу - заключается в том, что "дружба" - это как раз то, что к этой самой tight coupling приводит.
Про оценку ценности тут, конечно, пусть каждый сам приценяется ...
dixi :-)
Elvis-модификатор доступа в C#