Pre Scriptum (добавленный Post Factum)
Внимание! Опасность! Статья с элементами юмора. Пусть, шуток совсем немного (в общей доле всего материала, пожалуй, где-то сотые доли). Но из-за этого статью заминусовали. По делу же критики не увидел. Забавно, что после публикации и 2-х минут (реально, без шуток) не прошло, а уже сразу 2 минуса. «Не читал, но осуждаю»?
Не понимаю. Имхо, всегда приветствовалась живая подача материала, с юмором, который расслабляет, настраивает на игривый лад (а известно, что в не напряженной, игровой форме лучше всего идет усвоение). Совсем сухо - это справочник читать. В конце-концов ~ «вам без шушочек или ехать»?
Специально старался сделать теплую, остроумною статью. Первую статью. Не говоря уже о том, что все это оформлять - реально великий труд, подготовка материалов, публикация nuget пакетов, расширений,... да хотя бы картинку оформить...
Сделал для вас, имхо, весьма полезный функционал («красота для тех кто пониматЪ», конечно) и выложил в открытый доступ.
Склоняюсь к тому, что больше нет смысла писать статьи (хотя идей много) — первая и последняя. Просто людям не нужно, так зачем такие усилия?
Надеялся, еще поднять карму (ибо ранее она была начальная, читал, не писал). Ага, Спасибо Большое! Спасибо за заботу, за доброту, за ласку...
Мечты сбываются, поднял — отрицательный рост кармы. Сам, кстати, не минусую, только плюсы ставлю. Даже если нахамят. Если есть что сказать — просто говорю, как и сейчас.
+ еще ссылка на мой коммент-разъяснение по теме (открывать в новой вкладке - ибо, похоже, недоработака хабра в работе со ссылками в markdown)
Чаль путхактыримнида ▶ 잘 부탁드립니다
(кор.) ~ Пожалуйста, позаботьтесь обо мне.
Так принято говорить в Корее, впервые вливаясь в какое-то сообщество (новая школа, работа и другие похожие поводы... в моем случае — первая здесь статья). И делать поклон.
(может и вы читали цикл «Косплей Сергея Юркина»?)
«На наши щи» (vs кимчи): Бью челом бояре! Паки, паки...!
И надеюсь найти друзей (дружескую поддержку) не только в C#, но и здесь.
Как адепт функционального программирования (ФП), всегда стараюсь выстроить так систему классов приложения, чтобы классы в идеальном случаем имели только readonly публичные свойства (публичные это как минимум, а так — все). Братья адепты (и сестры, короче — сиблинги) тут меня хорошо поймут.
Не знаю нужно ли объяснять насколько это проще, понятнее, когда имеешь дело с такими классами? Наверное нет, ведь тут собрались те еще «тертые калачи» в программировании (хотя, не исключено, что они еще покажут мне, где кузькина мать зимует и докажут, что, наоборот, неправ во всем и в этом вопросе в частности).
Ну а для менее искушенного читателя лишь замечу — гляньте на тот же string C# класс. Как же проще понимать логику программы при работе со string экземплярами, когда знаешь, что их невозможно изменить.
А беззаботность в многопоточности? Недаром ФП ныне один из основных трендов в развитии языков.
А что делать? Кремневая микроэлектроника уже фактически уперлась в физический предел и дальнейший прогресс идет по пути наращивания числа ядер, многопоточности. Благодаря же неизменяемости в ФП, хороший компилятор с функционального языка умеет сам автоматически распараллеливать, создавать на выходе многопоточное приложение из кода, где вручную этого даже не было предусмотрено.
Как бывший активный плюсовик, а сейчас разве что пассивный (только не подумайте чего) и уже «давненько не брал в руки шашки». Как то в процессе разработке одного приложения на C# столкнулся с тем, что возжелал странного — friend-ов аки в плюсах, ибо уж больно они, если бы были в C#, вписались бы, решая архитектурную проблемку в том приложении.
Вы спросите, причем тут ФП? И будете правы. Просто тут чистый авторский произвол, волюнтаристский, авторитарный стиль написания статьи (понять и простить). Однако, все же поясню эту свою «колокольню».
Friend-ы, конечно, не входит в инструментарий академического ФП, но эта фича позволяет минимизировать «ущерб», когда пытаемся подражать стилю ФП в не ФП языках. Точнее в нечистых (свят, свят, свят... сгинь нечистый) ФП языках, ибо функциональные фичи сейчас есть везде.
Пусть некое свойство все же изменяемое, но мы строго локализуем случаи, когда такие изменения допустимы, не допуская до «комиссарского тела» кого попало. Может трогать лишь очень-очень узкий круг («член ЦК» там), для всех остальных же, все приблизительно выглядит как в чистом/непорочном ФП.
И тут может было бы более корректно говорить в терминах инкапсуляции, открытости/закрытости... Но влюбленные все видят через призму объекта своего обожания, так что вот так вот вывернулось в мозгах (еще раз понять и простить, даже бороду готов отрастить).
Вот, учитесь натягивать совушку на глобус.
Так или иначе, но именно ФП было побудительным мотивом, вдохновило на сей труд.
Ладно, оставим «хвилософию».
От абстрактной философии к суровой правде жизни
Вот вполне себе жизненная ситуация:
Есть условный «я» (class Me) и у меня есть друг (class MyFriend). Причем бедный друг (почти «бедный Йорик», но «еще жив курилка!»).
И пусть, с одной стороны, у меня есть 100 рублей (св-во Rubles).
А с другой, ведь «не имей сто рублей, а имей сто друзей», а также у друзей ведь все должно быть «на лапопам», не так ли? Так что готов разделить с ним рубли. Хочу дать другу «ключи от квартиры где деньги лежат» — приходи дорогой, бери половину рублей из тумбочки.
class Me {
public decimal Rubles { get; set; } = 100;
}
class MyFriend
{
public void AcceptMoney(in Me me)
{
decimal half = me.Rubles / 2;
me.Rubles = half;
Rubles += half;
}
public decimal Rubles { get; private set; } = -40;
}
Однако, очевидно, в такой реализации мои рубли открыты для всех, а не только для друга (так что вместо класса Me тут более подошло бы название Loh).
В плюсах тут можно было бы объявить класс MyFriend как friend для класса Me, где Me.Rubles было бы свойством открытым только для чтения, а вот к закрытому полю под этим свойством класс MyFriend как раз и имел бы доступ.
Прежде чем представить C# решение, приблизим ситуацию к еще более реальной: Пусть также у меня есть еще и юани (Yuans). А вот это уже святое (чай не уходящие в анналы истории всякие баксы с еврами, если верить пропаганде). К тому же, про юани народная мудрость ничего не говорит. Так что ни-ни, никому, приватные мои юанчики — другая (бронированная) тумбочка должна быть под надежным шифром.
Не правда ли хорошо вам всем знакомая жизненная ситуация?
Вот как можно реализовать аналог плюсовых friend-ов:
///////// Реализиция friend-ов:
class Me
{
public interface IFriend
{
// Setter
static protected void setMoney(in Me self, in decimal value)
=> self.Rubles = value;
}
public decimal Rubles { get; private set; } = 100;
public decimal Yuans { get; private set; } = 100_000;
}
class MyFriend : Me.IFriend
{
public void AcceptMoney(in Me me)
{
decimal half = me.Rubles / 2;
Me.IFriend.setMoney(me, half);
Rubles += half;
}
public decimal Rubles { get; private set; } = -40;
}
///////// Тестируем:
static class Test
{
public static void Run()
{
var me = new Me();
var myPoorFriend = new MyFriend();
log(me, myPoorFriend);
myPoorFriend.AcceptMoney(me);
log(me, myPoorFriend);
}
static void log(in Me me, MyFriend friend) =>
Console.WriteLine($"me: {me.Rubles}₽; friend: {friend.Rubles}₽");
}
выполнив тест, ожидаемо получим:
me: 100₽; friend: -10₽
me: 50₽; friend: 10₽
Красота — и други сыты и юани целы.
Очевидно, здесь доступ будет разрешен только и только для друзей (кто является наследником от Me.IFriend интерфейса).
А посмотрев на Me класс в IDE, например, в Visual Studio:

Сразу видим кто допущен.
Более того, ведь не обязательно тут должен быть именно сеттер (setMoney), это избыточно «щедро». Здесь расчет на то, что друг, имеет строгий моральный кодекс и не возьмет больше половины.
А если нет? И если тумбочка подключена к кредитно-банковской линии? Тогда ведь он может снять и 100_000 ₽, загнав меня в минус. Простим, понять его можно — на 10 ₽ нынче особо сыт не будешь (тут ранее погорячился заявляя такое).
Чтобы не вводить друга в искушение (ибо сказано «да не введи нас во искушение»), можно так сделать:
class Me
{
public interface IFriend
{
static protected decimal TakeMyHalfMoney(Me self)
{
decimal half = self.Rubles / 2;
self.Rubles -= half;
return half;
}
}
public decimal Rubles { get; private set; } = 100;
public decimal Yuans { get; private set; } = 100_000;
}
class MyFriend : Me.IFriend
{
public void AcceptMoney(in Me me)
=> Rubles += Me.IFriend.TakeMyHalfMoney(me);
public decimal Rubles { get; private set; } = -40;
}
Здесь уже дали более тонкий доступ, разрешив другу делать только и только то, что хотели,
Анализ, сравнение с C++
Буду сравнивать с теми плюсами, которые сам юзал в оное время. Что там наворотили в последних редакциях не проверял — вдруг что-то и дополнилось в плане friend-ов.
Но поскольку эта глава определенный вбоквел к статье, и интересна разве только тем кто «и вышивать могет, и на машинке тоже...» и знает C# и C++ как кот Матроскин, то даже не стал проверять — на тему статьи это совершенно не влияет, чтобы этим озабочиваться. Но если что, буду благодарен активным плюсовикам за активные комменты.
• Большой плюс: IFriend дает доступ не ко всем закрытым членам, а позволяет тонкую настройку, где можно обернуть это логикой, защитить от неправомерных операций (vs: вспомним как для случая доступа к непосредственному сеттеру можно загнать тумбочку в кредитную кабалу). Кроме того, в плюсах для friend открылся бы доступ и к «никому не дам» юанями (к приватному полю под этим свойством) так что прощай юанчики (цзай цзянь юанчики 再见 元).
• Минус: В C++ весь контроль над друзьями находится на стороне класса (в отличии от наследования MyFriend : Me.IFriend), что по идее более правильно.
С другой стороны, интерфейс может иногда быть и плюсиком, если класс находится в другой библиотеке, а мы хотим воспользоваться его дружелюбными фичами, которые он предоставляет.
И вам не докучают с просьбами добавить в друзья (или лезут в код, редактируя ваш C++ класс) — один раз написали C# класс, определив границы его дружелюбия, и отдыхаете.
Есть и определенный контроль. IDE в помощь — она всегда покажет разработчику Me.IFriend интерфейса кто подключился в друзья. Как уже отмечали, всегда можно посмотреть список друзей:

• Большой минус: в отличии от плюсов, не можем сделать другом только для конкретного метода другого класса.
В общем несколько разные подходы с определенным балансом своих плюсов и минусов.
Но это еще не конец. Попробуем еще один, имхо, красивый подход, не только наведя порядок аки в плюсах, но и более того.
Порядок (красота для тех кто пониматЪ)
Для того чтобы сделать так же как в плюсах доступ только для конкретного метода, а также контроль друзей на стороне класса, можно воспользоваться такими Roslyn технологиями как Analyzers или/и Incremental Source Generators.
Что же, «пацан сказал — пацан сделал» (хотя некоторые говорят, что лучше звучит: «пацан сказал — девочка сделала», но я олдскульный человек).
Ссылка на проект
Описывать внутреннюю реализацию этого Roslyn проекта здесь нет смысла — обычный проект такого рода. Речь в статье совсем не о техниках Roslyn.
А о том какой инструментарий предоставляется для удовлетворения наших хотелок и как его использовать. Каков предлагаемый «интерфейс», а не второстепенное дело деталей его реализации (по ним см. код на гитхабе).
Вот на этом далее и сосредоточимся.
Элвис атрибут.
Имеется [OnlyYou] атрибут (можно было назвать и OnlyFor, но просто в честь известной песни ▶, так что можете считать что перешли от темы friend к теме girlfriend):
[AttributeUsage(AttributeTargets.Class
| AttributeTargets.Method
| AttributeTargets.Property,
AllowMultiple = true)]
class OnlyYouAttribute : Attribute
{
public OnlyYouAttribute(Type type,
params string[] members) { }
}
Как видим из определения, атрибут можно применять для любого метода или свойства (мембера), класса или интерфейса, статического или не статического.
Теперь можем следующим образом избавится от того недостатка, когда не было возможности как в плюсах сделать другом только и только конкретный метод другого класса:
class Me
{
public interface IFriend
{
[OnlyYou(typeof(MyFriend),
nameof(MyFriend.AcceptMoney))]
static protected decimal TakeMyHalfMoney(Me self) {...}
}
...
}
class MyFriend : Me.IFriend
{
public void AcceptMoney(in Me me)
=> Rubles += Me.IFriend.TakeMyHalfMoney(me); // ok
public void CantAcceptMoney(in Me me)
=> Rubles += Me.IFriend.TakeMyHalfMoney(me); // err
...
}
Здесь комментарии err и ok показывают, где будет ошибка компиляции, а где она пройдет успешно.
Только этот пример избыточный — теперь интерфейс по сути не нужен (см. следующий пример).
Analyzer проверяет вызовы (использование) мемберов, которые отмечены данными атрибутами. И только в тех местах вызовы этих мемберов будут разрешены, которые описаны в параметрах [OnlyYou] атрибутов. Для всех же остальных вызовов (в местах, которые не описаны в параметрах атрибутов) будет ошибка компиляции.
Для одного мембера, как видно из определения атрибута, можно задать несколько таких атрибутов.
Вот более чистый (без Me.IFriend интерфейса) пример использования этого атрибута:
class Me
{
public decimal Rubles { get; private set; } = 100;
[OnlyYou(typeof(MyFriend),
nameof(MyFriend.AcceptMoney))]
public decimal TakeMyHalfMoney() {
decimal half = Rubles / 2;
Rubles -= half;
return half;
}
}
class MyFriend
{
public void AcceptMoney(in Me me)
=> Rubles += me.TakeMyHalfMoney(); // ok
public void CantAcceptMoney(in Me me)
=> Rubles += me.TakeMyHalfMoney(); // err
public void AcceptMoneyFromLoh(in NotMe me)
=> Rubles += me.TakeMyHalfMoney(); // ok
public decimal Rubles { get; private set; } = -40;
}
class NotMe // Loh :)
{
public decimal TakeMyHalfMoney() => ... 1000;
...
}
В Visual Studio это так выглядит:

Аналогично для свойств:
class Me
{
[OnlyYou(typeof(MyFriend), nameof(MyFriend.SetMoney))]
public decimal Money { get; set; } = 100;
}
class MyFriend
{
public void SetMoney(in Me me) // ok
{
var half = me.Money / 2;
me.Money = half;
me.Money = half;
me.Money += half;
me.Money -= half;
me.Money *= half;
++me.Money;
--me.Money;
me.Money++;
me.Money--;
}
public void CantSetMoney(in Me me) // err
{
var half = me.Money / 2;
me.Money = half;
me.Money += half;
me.Money -= half;
me.Money *= half;
++me.Money;
--me.Money;
me.Money++;
me.Money--;
}
}
И картинка из Visual Studio:

Свойства можно читать, но изменять их могут только друзья.
Попробую ввести такую терминологию: про термин «друг» (класс MyFriend в примерах) вопросов нет, а вот того с кем хотят все дружить, кто всех привлекает (класс Me в примерах), назову «аттрактором» (придумайте лучше? социофил? другофил(фу!)?).
Собственно это альтернативный, уже со своей спецификой и возможностями, способ задания друзей.
Основная специфика, что в отличии от плюсов не позволяем залезать в приватную часть класса. Считаю это большим благом — то что приватно и должно оставаться приватным. Тем более что всякого приватного у класса может быть очень много, и выставлять все это хозяйство на показ (как в плюсах) тот еще «эксгибиционизм». Да еще и все это (надо - не надо) друзья могут курочить как угодно, внося хаос, так что может так случится, что с такими «друзьями» и врагов не понадобится.
Здесь же точечная, ювелирная работа — определяем публичный мембер, где разрешаем друзьям действовать только в тех рамках которые сами определили как безопасные. Для остальных же (недружественных) классов этот мембер становится фактически как если бы он был определен приватным в классе аттракторе.
Написал как Analyzer (FriendAnalyzer) так и Incremental Source Generators (FriendGenerator).
Это предварительные демонстрационные проекты.
Где FriendGenerator сделан только для методов. Его просто попробовал на случай если вдруг генератор будет быстрее, благодаря хваленым кэшам в новых инкрементальных генераторах. И для генератора не нужно добавлять код класса атрибута, он сам его добавляет в компиляцию.
Но что быстрее — не стал устраивать сравнительные «забеги», решив все же остановится на Analyzer варианте, который себя и так хорошо показал. Сгенерировал для него 1000 cs-файлов с простыми классами друзей, добавил в проект и не заметил каких-либо тормозов при редактировании кода в Visual Studio (для определенности: Visual Studio 2022, 17.14.16).
Было беспокойство, что при редактировании кода будет пере-анализироваться весь проект. Ибо как в этом плане ведут себя аналайзеры, как-то не встречал чтобы про это писали в доках по ним. Добавил в аналайзер короткий (в 100ms) консольный бип, фактически трещетку. И убедился, что аналайзер действует очень экономно — анализирует только тот файл на который смотрим. И даже похоже учитывает прокрутку, т.е. только тот кусок что видим. При открытии файла выдает очень короткую дробь, а при первой (только первой) прокрутке можно услышать одиночные щелчки. При редактировании — та же очень короткая дробь, несмотря на > 1000 зависимых файлов.
У [OnlyYou(type, method1, prop1..)] вторым аргументом идет список имен методов/свойств. Для методов же, как известно, возможна перегрузка. Однако в этом атрибуте такие перегруженные методы не различаются — атрибут принимает только имена. Так что если method1 перегружен, то все его варианты будут френдами.
И здесь можно попробовать реализовать более тонкий подход - например, передавать не просто имена методов, а сигнатуры, например: "method1(string, int)".
Однако, такой подход со строкой сигнатуры не очень нравятся. Ибо здесь нужно и не ошибиться в ее составлении, и в процессе разработки следить за тем, чтобы в этой строке всегда была именно актуальная сигнатура, и следить за регистром букв...
Поэтому пошел другим путем. Были определены еще 2 атрибута:
[AttributeUsage(AttributeTargets.Method
| AttributeTargets.Property,
AllowMultiple = true)]
class OnlyAliasAttribute : Attribute
{
public OnlyAliasAttribute(Type type,
params string[] aliases) { }
}
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
class FriendAliasAttribute : Attribute
{
public FriendAliasAttribute(string alias) { }
}
Атрибут [OnlyAlias] играет ту же роль что и [OnlyYou], только вторым аргументом теперь передаем не имена методов, а их алиасы. Алиас же для метода можно назначить с помощью атрибута [FriendAlias].
Пример:
class Me
{
public const string AcceptMul = nameof(AcceptMul);
public decimal Money { get; private set; } = 100;
[OnlyAlias(typeof(MyFriend), AcceptMul)]
public decimal TakeMyHalfMoney() {
decimal half = Money / 2;
Money -= half;
return half;
}
}
class MyFriend
{
public void AcceptMoney(in Me me)
=> Money += me.TakeMyHalfMoney(); // err
[FriendAlias(Me.AcceptMul)]
public void AcceptMoney(in Me me, int mul)
=> Money += mul * me.TakeMyHalfMoney(); // ok
public decimal Money { get; private set; } = -40;
}
Так что для случаев перегрузки методов, можно использовать этот инструментарий.
Рекомендую именно так определять алиасы — константами в классе аттрактора. Такое единственное место определения убережет от возможной ошибки в значениях алиаса в первом и втором атрибуте (в принципе можно даже добавить специальное правило в аналайзер, которое будет заставлять именно так делать).
Рассмотрим такой случай:
Пусть для одного и того же MyFriend класса на один и тот же перегруженный AcceptMoney метод (для не перегруженного вопросов нет) из нашего примера выше, навесили сразу и [OnlyAlias] и [OnlyYou] атрибуты:
class Me
{
public const string AcceptMul = nameof(AcceptMul);
public decimal Money { get; private set; } = 100;
[OnlyAlias(typeof(MyFriend), AcceptMul)]
[OnlyYou (typeof(MyFriend), AcceptMoney)]
public decimal TakeMyHalfMoney() {
decimal half = Money / 2;
Money -= half;
return half;
}
}
Тогда имеем дилемму:
[OnlyYou] разрешает оба варианта AcceptMoney,
[OnlyAlias] же, со своей стороны, разрешает только метод под AcceptMul алиасом.
Кто будет прав?
Сделал по принципу «или» — объединение множеств методов первого и второго атрибутов, а не пересечение. Т.е. в нашем примере оба перегруженных метода будут разрешены.
Так, думаю, более логично.
Но в принципе возможен выбор и в пользу «и» (пересечения множеств методов). А то и вовсе, определить в аналайзере специальное правило, которое бы запрещало такие случаи.
Как видно из определения [OnlyYou] атрибута его можно применять и к классам. С очевидным смыслом — разрешать доступ к мемберам аттрактора только избранным типам (далее еще немного «порастекаюсь по древу», а потом будет сразу общий пример кода).
Прим.:
кстати, опять отметим здесь такое же (как и для friend-методов) благое расхождение уже с friend-классами в плюсах.
[OnlyYou] на аттракторе «бьет» [OnlyYou]/[OnlyAlias] на принадлежащих ему мемберах. Т.е. если у аттрактора запрещены все типы кроме некоторых привилегированных, то на его мемберах уже никто не сможет пролезть, пытаясь определить на них атрибуты разрешающие доступ запрещенным типам.
Как если у короля, «нашего королька — как это я называю» (класса аттрактора) есть враги (недружественные типы), то если его подданные (его мемберы) вдруг попытаются с ними дружить, то это будет расценено как предательство, и СБ королевства (аналайзер) это будет пресекать («расстрелы, только расстрелы...!»). В принципе можно даже добавить в аналайзер правило, согласно которому, запрещено использовать [OnlyYou] на мемберах, если параметр типа применяемого атрибута не один из тех, что разрешены в атрибутах на классе аттракторе.
Пример:
[OnlyYou(typeof(MyFriend))]
class Me
{
public decimal Money { get; set; } = 100;
public void Method1() { }
[OnlyYou(typeof(MyFriend),
nameof(MyFriend.CanInvoke1))]
[OnlyYou(typeof(NotMyFriend), // no effect
nameof(NotMyFriend.Some1))]
public void Method2() { }
}
class MyFriend
{
public void CanInvoke1(in Me me) => me.Method2(); // ok
public void CanInvoke2(in Me me) => me.Method1(); // ok
public void CanSet(in Me me) => me.Money = 200; // ok
public void CantInvoke(in Me me) => me.Method2(); // err
}
class NotMyFriend
{
public void Some1(in Me me) => me.Method1(); // err
public void Some2(in Me me) => me.Method2(); // err
public void SomeSet(in Me me) => me.Money = 0; // err
}
Хотя тут можно было реализовать и другой принцип, типа: «вассал моего вассал не мой вассал».
[OnlyYou] на классе — почти как новый модификатор доступа. Очень нравится не так давно введенный file модификатор в C#, не редко применяю его. Здесь же получается еще более селективно — фактически разрешаем доступ только для избранных классов, только тем классам, кому это реально нужно, отсекая прочих праздно-досужих, а то и «шпионов» :)
Хотя в текущей реализации это не полноценный модификатор доступа, ибо, например, читать те же открытые свойства аттрактора по прежнему можно. Но, думаю, лучше бы и это все запретить, вернее сделать опциональным, задавая параметром в атрибуте.
В C# как-то якобы похоже регулировать доступ можно через специальный импорт, например:
using SpecificClassAlias = Utilities.SpecificUtilityClass;
Однако, в [OnlyYou] варианте доступа мы, во-первых, задаем это на стороне аттрактора.
Во-вторых, если в неймспейсе Utilities куча других классов которые нам нужны, то перечислять их все, да еще назначать алиасы (что мне не нравится — даже если делать их совпадающими с именами классов, то при переименовании классов будут несоответствия).
А если попытаться сделать:
using SpecificClassAlias = Utilities.SpecificUtilityClass;
using Utilities;
то второй using просто обесценит первый.
И в-третьих, с [OnlyYou] глянув на аттрактор — сразу видим, кому и только кому и только что разрешено. Имхо, это ценная информация для понимания кода программы (как другими, так и самому автору год спустя). Больше порядка, «пуговицы в ряд». В отличии от юзингов, где все на совести пользователей, и ваши классы-«кровиночки» все так же могут «полоскать» кто только пожелает, «в любых позах».
В общем, регулировка доступа через юзинги — «это другое» (c).
Вот и описан базовый функционал.
Только в аналайзере все же больше всяких нюансов, особенно с наследованием классов. Множество примеров ситуаций использования можно найти в github репозитории — см. там TestAnalyzerLib тестовую либу. Есть открытые темы — их примеры можно найти поиском по !!! строке в этой либе (а то и добавьте своих открытых тем, уверен их можно найти). Также пока не делалась реализация для полей (да и нужно ли?) ...
Хоть велико-скромно (ибо самый скромный человек в мире) и назвал это демонстрационным проектом, но его вполне можно использовать. В отличии от к-л сторонней либы, которая при ее использовании может подвести вас, если она «сырая». FriendAnalyzer ничего в принципе не может нехорошего привнести в ваш прекрасный код, а может лишь выдавать ошибки компиляции, чем попросит вас сделать ваш код еще прекрасней, понятней, яснее.
Feel free, если кто заинтересовался этой темой (особенно если кто хорошо шарит в анализаторах/генераторах), и желает подключится к развитию этого проекта (стать контрибьютером).
Весь код в репозитории. При компиляции аналайзера создаются Friend.Analyzer nuget пакет и крошечный FriendLib пакетик с атрибутами. Они опубликованы, так что welcome для подключения к вашим проектам.
Единственное, несмотря на то, что FriendLib пакетик о��ределен как зависимый пакет, который используется Friend.Analyzer пакетом, он почему-то автоматические не подключается, когда аналайзер добавляется в пользовательский проект.
Возможно потому, что аналайзер это аналайзер, т.е. он не линкуется в пользовательский проект, и поэтому возможно и FriendLib не добавляется за компанию. Так что сейчас нужно добавлять FriendLib самим. Но если это не фича и все же возможно как-то автоматизировать это добавление, то буду благодарен за мудрое наставление.
(Как вариант такой автоматизации, так теоретизировал: определить в проекте аналайзера еще и генератор (Incremental Source Generator), единственной функцией которого будет добавлять код атрибутов при его инициализации. Ну если «прокатит» аналайзер и генератор «в одном флаконе».).
Или, вообще, код этих атрибутов можно просто вручную добавить в код вашего проекта, благо они тривиальны.
А возможно эти фичи будут вам столь угодны, что захотите установить расширение в студию, и больше не заморачиваться с установкой пакетов аналайзера — тогда, как воскликнул бы Якубович, «расширение в студию!»
Еще, можно сказать, эти правила-ограничения вводят более строгий, тонко-настраиваемый вариант инкапсуляции. Даже хотелось бы, чтобы в языке ввели для этого какую-то удобную синтаксическую конструкцию, нежели вот так на атрибутах и аналайзере (в плюсах же озаботились включить в язык подобное).
Заключение (или программист в законе)
Ограничения важны. Они суть законы. Как в физике, где формула-закон жестко связывая разные величины тем самым накладывая ограничение, и пущенный камень уже летит не рамдомно, а с ограничением — по параболе.
В некотором смысле, законы-ограничения и определяют тот или иной язык программирования. Одно из сильнейших ограничений — неизменяемость, а какая красота порождается в ФП. Или в ООП та же инкапсуляция и др....
Хотите полной свободы — программируйте в машинных кодах. Неудобно? Кто бы спорил.
Законы из хаоса наводят порядок и красоту, везде: в физике (простите, опять первой ее упоминаю, пристрастен — как физик по базовому образованию), в других науках... в человеческом обществе и, конечно, в языках программирования.
Что было бы, если бы камень который вы уронили не упал на землю, а ударил по голове? Пол в квартире внезапно проваливался?... Каждый хочет контролировать свою жизнь, жить в среде, которую он может предсказывать. Законы именно это и позволяют. Так же и в программной среде. И, надеюсь, эти Элвис/friend законы, сделают эту среду чуточку более предсказуемой, внесут, в натуре, больше управляемости в этот важный аспект вашей жизни, дорогие коллеги-кореша :)
Понравился стих (по памяти, кто написал гугл точно не смог определить):
Не терпит красота канона,
Но и без формы гибнет красота,
А форма требует закона.
Заранее спасибо за ваши лайки и комменты — они «греют».
Ой, а могут и огреть (зачем сказал? зачем навожу людей на неправильные мысли? хорошо же выше строчкой закончил статью... ой, дурааак)
P.S. Поделюсь опытом написания статей на хабре (скажете: вот нахал — только-только первую статью написал, а уже опытом делится!):
Фух!
(вот и весь пока опыт — оказывается тот еще труд; или трудны только первые 100 статей?)
P.P.S. «выпить йаду» или «пиши есчо»?
Ссылки:
код на github
Friend.Analyzer и FriendLib nuget пакеты
vsix расширение
Комментарии (46)

Cheater
03.11.2025 14:11От стиля статьи прямо как в ламповые 00-е кинуло если не раньше :)
Про плюсы не понял откуда там гранулярный доступ к членам класса. Такого в C++ нет, ни в современном, ни в старом. Есть множество способов указывать друзей (конкретная ф-я, конкретный класс, конкретный метод чужого класса, шаблон...), но любой друг всегда видит всё. Гранулярный доступ к методам в C++ для друзей осуществляется через дополнительный набор ограничивающих интерфейсов поверх данного класса, одна из известных реализаций это идиома Attorney-Client https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Friendship_and_the_Attorney-Client.

VadimLL Автор
03.11.2025 14:11В C++ можно определять friend-ы только для конкретных функций/методов (не целиком для классов):
https://www.w3schools.com/cpp/cpp_friend_function.asp(почему-то в конце ссылки символ  добавляет, удалите его после прохода по ссылки и перейдите заново)
Или что имеете ввиду?

SWATOPLUS
03.11.2025 14:11А можно выжать воду и написать техническую статью? А то мне понадобились значительные усилия, что бы понять о чем статья, несмотря на то, что я знаком и с с# и с с++.
Суть статьи это создание friend-классов (в терминологии c++), возможность определенного класса иметь доступ к не публичным членам другого класса. Но зачем, если в c# есть модификатор доступа internal? Есть ли кейсы где internal не достаточно?

VadimLL Автор
03.11.2025 14:11internal дает доступ всем классам в сборке. Пример же (кейс) c# друзей как раз сквозным образом проходит через всю статью.

SWATOPLUS
03.11.2025 14:11Я делаю сборку библиотеки и только мои классы могут лезть в internal, пользователи библиотеки не могут трогать internal.
Это расширяет возможности разработчикам библиотеки, но снижает пользователям библиотеки (что б не сломали). А для friend классов какой кейс?

VadimLL Автор
03.11.2025 14:11Если в вашей библиотеке пара классов, то может и не вопрос (и то с оговорками). Только разве в реальных проектах часто так? А по кейсу уже писал — пример, сквозным образом проходит через всю статью.
Как знаете, помимо internal есть разные модификаторы доступа. В статье, можно сказать, предлагается еще один, так сказать, elvis-модификатор. Если не видите его специфику, разницу с internal, то, честно говоря, не знаю, что тут еще добавить, кроме того, что уже подробно описано в статье.
Wolfdp
03.11.2025 14:11Да хоть миллион классов, насколько часто нужно именно запрещать доступ всем остальным, кроме конкретного класса? Мне кажется это очень специфическая задача и проще (возможно даже и лучше) прокинуть доступ через интерфейс/статику/вложенность.
Хотя строго говоря это попахивает нарушением инкапсуляции. Если изменение извне запретили для всех, значит это может нарушить логику работы класса. Если даем разрешение внешнему коду -- есть смысл тогда просто "распаковать" доступ полностью, и делегировать управление внешнему коду.

VadimLL Автор
03.11.2025 14:11По моему опыту не так уж и редко для задач со сложной логикой. Если писать к-л типовой проект, бэкэнд рядового интернет магазина какой-нибудь, то там, да, все более-менее линейно. Другое дело, когда задача со сложной логикой.
Как говорил в статье, следуя ФП дао-пути, всегда так стараюсь выстроить систему классов приложения, чтобы классы в идеальном случаем имели только readonly публичные свойства. Увы, как раз для «нелинейных» задач это не всегда просто удается (а если делать не просто, то обычно уж слишком не просто, непроизводительно, не стоит свеч). Некоторые классы оказываются семантически связанными. Какие-нибудь пары например, при работе одного класса-обработчика необходимо скорректировать некоторое свойство в другом. Но в проекте еще очень много классов, которые видят и читают это свойство, этот класс. Поэтому только ради одного кейса открывать его для всех (public { get; set; }) совсем не хочется.
Особенно в больших, командных проектах иногда видел, что чуть ли не все свойства (надо - не надо) имеют публичные get-set. Вот смотришь на миллион таких классов (особенно на чужие, да и на свои сколько-то времени спустя) и чешешь репу — да где же только в этом огромном коде с миллионом классов и миллионом мемберов в них (ладно, не буду вас цитировать — ограничимся хотя бы 1000) не треплют эти свойства? Напоминает пресловутый антипаттерн глобального состояния, только, имхо, хуже, ибо это состояние, размазано по массе классов и попробуй разберись, как оно меняться, если каждый может изменять каждого. А вот если все неизменяемое, то все куда-куда упрощается, намного проще понять логику. И описываемые в статье инструменты позволяют приблизится к этому идеалу.
У меня в проектах практически все классы неизменяемые. Имеются только очень выборочные исключения для некоторых мемберов, которые открыты, но не тысячам других классов, так что они были бы способны их «матросить», а только одному/двум (intellisense же при большом числе классов с их массой мемберов в каждом тут слабо помогает). Смотришь на них и все прозрачно, видишь явную логическую связь — не тысячи возможных нитей-связей, за которые могут все дергать, а одна/две явно прописанные.
прокинуть доступ через интерфейс/статику/вложенность
ну да, про этот как раз в первой половине статьи и писал (см. Me.IFriend)
попахивает нарушением инкапсуляции.
Про инкапсуляцию так бы сказал (собственно писал про это): не нарушает ее, в скорее дополняет еще одним инструментом. Вводит еще один, можно сказать, модификатор доступа, более селективный модификатор (elvis модификатор). И лично мне нравятся такие тонкие инструменты — как инструменты ювелира vs инструменты слесаря, ими уже можно всякую тонкую, так сказать, «часовую механику» налаживать, а не только рубить метал зубилом с молотком. Яйца фаберже vs рессоры от трактора беларусь :)
Еще дополнительные аргументы (там где про объектную модель и связность) привожу в ответе на другой пост:
см. https://habr.com/ru/articles/961264/#comment_29067094

a-tk
03.11.2025 14:11Как же тяжело пробиваться через обилие словоблудия на квадратный пиксель

VadimLL Автор
03.11.2025 14:11Хорошо, что не так?
Пробую понять...
Первый с хвостиком абзац — приветствие человека первый раз написавшего здесь статью. Дань вежливости.
Далее немного славословий любимому ФП. Ведь тем более далее будут ссылки на ФП. Совсем-совсем немного! В сентябре вот решил отрефрешить свои знания по Haskell выбрал самую краткую книжку по нему, так там только первые 4 главы (немаленькая часть книги) «словоблудили» в вашей терминологии. И нормально, те кому не нужно (как мне), просматривали по диагонали, кивая головой. Здесь что-ли «пробивались»? Это же как раз легкая часть.
Кстати, после этой статьи была мысль (напрасная? опыт не удался) как раз написать статью по Haskell.Кроме того, показалось, что читающим будет интересен «линк» между friend и ФП (может и баян, но сам лично не встречал). Такие семантические/ассоциативные связи весьма приветствуются — чем их больше, тем лучше, авторитетно считается, что они активируют креативность у людей. Может и здесь кого-то на что-то, уже свое, по цепочке ассоциаций сподвигло бы.
Кроме того, как писал, это было моей мотивацией. И также известно, что мотивация, мысль-исток из которой развилась к-л теория, важна для лучшего, более глубокого понимания.
Поэтому и есть вводная часть.
Потом идет львиная, центральная часть статьи. Тут уже чисто техническая информация (как вы любите), чуть-чуть скрашенная юмором (простите). Через эти крошки юмора что-ли «пробивались»? Имхо, обычно это как раз самые легкие фрагментики. Ну, если не брать героев анекдотов определенной национальности (все нации уважаю — не я такой, анекдоты такие), до которых долго доходит юмор и они «подвисают».
Есть образы для лучшего восприятия. Вот у вас тоже хороший образ, да еще с юмором: «словоблудия на квадратный пиксель». Так и хорошо выражаетесь. Или только вам можно?И в конце небольшое заключение. Имхо, интересная (мне точно) мысль о важности законов-ограничений. Есть поверье, что чем больше дается свободы, тем лучше. Здесь же «встречная» мысль. Те же стихи — ограничение на рифму, дает красоту, углубляет смыслы (еще: «тесные врата и узкий путь ведут в жизнь вечную» — вот, еще за цитату заминусуйте). Считал важным это высказать, ибо полагаю важным философским принципом, важным обобщением, взяв за базу которое, кому-то может прийти идея как придумать свои-другие ограничения (так что а, вдруг, вообще придумаете что-то по-круче принципов-ограничений ФП).
Эта часть можно сказать мета-выход за рамки friend-ов. Как отчасти и другие части (каламбурчик) — не только о friend-ах речь (о них и вовсе можно не упоминать, а говорить о новом elvis-модификаторе доступа), но в заголовки же все содержание не выскажешь, а тут приходят и критикуют, мол сбился с темы заголовка (а там, между прочим, есть уточнение: «однако, более того»).Поэтому и есть заключительная («словоблудная») маленькая часть.
Может тут просто какое-то взаимное недопонимание?

a-tk
03.11.2025 14:11Сократим до поста:
Я захотел сломать объектную модель C# и сделать анализатор, который позволит мне это сделать. Я не знаю, что такое generic-атрибуты и зачем нужно ключевое слово in (но использую его), и сейчас нагорожу свой огород так, что теперь код должен знать своих клиентов, что порождает избыточную связность кода.

VadimLL Автор
03.11.2025 14:11Спасибо за code review, правда первый раз от коллеги принимаю его в такой неэтичной форме.
Я не знаю, что такое generic-атрибуты
Почему решили что не знаю? Да еще в категоричной форме высказались ЗА МЕНЯ (да, давно меня так не насиловали! интересно какой у вас опыт работы? а то не верится, что есть коллективы где так принято). Хотя бы спросили — знаю ли?
Так вот: если здесь использовать generic атрибут, то аналайзером не смогут воспользоваться те у кого код написан на C# 10 и ранее.
Не уверен, что стоит отсекать таких пользователей. У меня нет статистики, и просто не знаю насколько массово люди переходят на новые версии. Так что, как говорится, «не уверен — не обгоняй». Если доказательно сообщите мне такую статистику, что, скажем, 99% уже полностью перешли, даже в апгрейдах старых проектов, то можно будет перейти только на этот вариант.
Запись при этом будет чуть лаконичнее, но сути это абсолютно никак не изменит. Чисто декоративное изменение.
Можно сделать как дополнительный вариант задания атрибута к уже существующему для тех кто на C# 11 и выше. Так и проект пока на уровне первого коммита и там, конечно, есть много что развивать о чем сам в статье писал.Почему сам ранее не подумал над таким возможным дополнительным вариантом?
Знаете, хлопот по проекту и так хватало и хватает, причем уже не чисто декоративного характера.
К тому же стереотип, что generic атрибуты, используются в виде:class GenAttribute<T> : Attribute { T _value; public GenAttribute(T value) => _value = value; } [Gen<string>("Hi")] class SomeClass { }т.е. для указания типа аргумента конструктора атрибута, собственно это базовая мотивация их введения в C# 11. Но, в данном случае, в случае использования для целей аналайзера, generic-параметр конструктора будет отсутствовать (в обычной же разработке такой вариант трудно представить).
------------------------------
Я не знаю... зачем нужно ключевое слово in (но использую его)
Опять.
Вероятно в вашем понимании in используется только для передачи больших структур по ссылке в целях оптимизации, чтобы избежать их копирование.
Если так, то почему так узко мыслите?
Использую in как приверженец ФП и ясности. Ибо это сразу вносит ясность, что аргумент метода нельзя переприсвоить. В Kotlin и Rust, как в более современных языках (а значит более склонных к ФП), все параметры по дефолту неизменяемые. Также, к сожалению, в C# нет такого для любых локальных переменных (как это есть в других языках).------------------------------
Я захотел сломать объектную модель C# и сделать анализатор, который позволит мне это сделать.
Да что же вы опять за меня утверждаете, телепат что ли, чтобы знать что я хочу? Вот прямо «доктор-зло» — хочу сломать объектную модель C#. Единственно верную и непоколебимую модель, что у вас в голове, как истину в последней инстанции?
Устал. Боюсь, что если буду писать про объектную модель, ее понимание, пути развития... то это может вылиться в тот еще холивар.
Так что лишь отмечу, что в плюсах friend-ы вполне себе спокойно существуют. Можно о них спорить. Мое мнение: любой инструмент можно как правильно использовать, так и извращенно до абсурда. В общем, головой пользоваться нужно.То что предлагается, как писал в статье, можно рассматривать как новый модификатор доступа подобно private, protected... но более селективный. Можно мембер сделать public только для избранных классов/методов... для остальных же будет как private. Это инструмент ювелира, а не слесаря от программирования. И, да, тут тоже нужно головой пользоваться.
Связность?
Вызов метода, изменение свойства в другом классе это тоже связность. Пусть в проекте 1000 классов, каждый с кучей своих мемберов. Но пусть для определенного мембера в определенном классе из всех их лишь некоторым по логике задачи реально требуется доступ. И ради одного кейса открывать доступ всем 1000?
Это так же как вместо private сделать какой-то мембер public, хотя это совершенно не нужно и даже вредно (если его пользование требует сопутствующих согласованных действий для сохранения консистентности данных класса). И надеяться, что никто из джунов-разработчиков других 1000 классов не воспользуются этой открытость (как минимум случайно, но чаще из за дурной креативности, не понимания очень сложной системы в целом, собственно хорошо если ее хоть главный архитектор понимает. данный же модификатор снимает все эти проблемы). И это можно назвать потенциальной связностью и она огромна. А так уменьшаем ее в 1000 раз. А элвис-атрибут, раз в друге этот мембер все равно вызывается и тем самым уже протягивает нить связности, тут ее особо не увеличивает. И, если хотите, то нет проблем определить в аналайзере правило, которое запрещало бы навешивать атрибут если он реально не используется.Также, оказывается, сколько же других аналайзеров тоже ломают объектную модель, выдавая ошибки компиляции в соответствии со своими доп.правилами к ванильному C#?
Если кто-то не может представить таких ситуаций, когда это может быть полезно, то на мой, сугубо личный взгляд, такой программист, либо имеет мало опыта, либо все время имеет дело с какими-то линейными задачками, типа всю жизнь штампует бэкэнды с типовой архитектурой, не сталкивался с реально сложными, нестандартными задачами. А так сам, если что, много чего не знаю.
------------------------------
Исключительно для того чтобы лучше донести до вас, зеркалю и пишу следующие утверждения от ВАШЕГО имени:
Я не знаю что такое элементарная этика.
Я считаю себя истиной в последней инстанции.
Я не смог понять материал статьи.
Я трахаю животных. (чисто для усиления понимания неэтичности)
...
Но зачеркиваю их, не подписываюсь под ними, ибо не позволяю себе такого.
Даже если я 100% уверен, что кто-то ошибается, то отношусь к нему с уважением, ибо «каждый человек имеет право на ошибку» (из старой восточной мудрости) и я уважаю его права.Психологи же скажут, что неуважительно, т.е. пытается унизить собеседника, тот, кто тем самым хочет за счет другого повысить свою самооценку, а значит внутренне глубоко ущербный человек (и как правило не отдает себе в этом отчета). Просто общий факт ни на кого конкретно не нацеленный, и если что, сам себя считаю во многих аспектах ущербным, «не ангел» (увы мне, и надо работать над собой).
Извиниться не хотите за неэтичное поведение?
Вы же извините, если был несколько резок, хотя и не переходил грань этики. Реакция определилась тоном вашего поста. Предпочитаю приятную, уважительную беседу (ту самую «роскошь общения» по Экзюпери), когда получив положительные эмоции от общения каждый выходит обогащенным. Даже если один оказался полностью не прав, то другой получает возможность уважительно помочь человеку, а это ценно само по себе, ибо через помощь другим и развивается, растет личность.

VadimLL Автор
03.11.2025 14:11P.S. Даже если принять ваши слова, что «ломает объектную модель C#» («нагородя свой огород»), хотя не согласен с таким словом — не ломает, а дополняет. Так и что? Модель ООП (или ФП) «ломает» модель чисто процедурного программирования. И что «ужас-ужас!»?
Каждая новая версия языка «ломает» предыдущую — ошибки компиляции для новых фич. Также как и многие-многие аналайзеры могут выдавать ошибки компиляции согласно своим правилам. Обычно придерживаются принципа обратной совместимости. А бывает, что и не придерживаются как с Python 2 и Python 3. Реально сломали, без кавычек. И что? Правильно ведь сломали — Python 3 лучше. Часто мне хочется чтобы и C# сломали, ибо накопилась «борода» легаси, что тормозит развитие, как накапливаются тормозящие гирлянды ракушек на днище старых кораблей.
Просто подключение данного аналайзера, абсолютно не влияет на ошибки компилятора, как если бы и не подключали. А вот дальше уже сам программист, если сочтет нужным, может управляемо «сломать» ванильный вариант, оставаясь в рамках C# синтаксиса, просто навесив определенные атрибуты. Подключая голову, а не безбашено лепя атрибуты на каждый мембер и класс. По моему опыту, для задач со сложной логикой это где-то один отмеченный мембер на сотню классов.
И вопрос: почему вы выбрали именно слово «ломает» («нагородя огород»)? Чтобы повысить за мой счет свою самооценку? (мол, смотрите все, вы дурак со своей программой и соответственно я умный на вашем фоне? или как?)

ValeriyPus
03.11.2025 14:11Суть в том, что теперь методы можно вызывать лишь из определенных методов.
Не хватает:
1) вызова только из определенных Namespace, из определенных классов\интерфейсов.
2) Вызывать отовсюду, кроме...
3) nuget-пакета с тестами
4) внятного стиля изложения

VadimLL Автор
03.11.2025 14:111) Да namespaces можно добавить для коллекции (также как и поля...). А "из определенных классов\интерфейсов" сделано (про это есть в статье).
2) В принципе можно добавить и инверсию, правда пока плохо представляю, где это может быть полезно.
3) Пакеты с тестами... Помилосердствуйте, это только первая версия. И так замаялся все это оформлять - реально труд великий с этой статьей, подготовкой материалов, публикаций nuget пакетов, расширений,... да хотя бы картинку оформить... И, как оказалось, заминусоваили. Склоняюсь к тому, что больше нет смысла писать статьи (хотя идей много), первая и последняя - людям не нужно.
4) Понять и простить.

Oceanshiver
03.11.2025 14:11На мой вкус вот эти плюсовые friends - это какой-то рудимент, который только запутывает логику приложения и делает зависимости менее прозрачными. Как хорошо, что его не стали тащить в C# разработчики языка.

VadimLL Автор
03.11.2025 14:11В C# много чего из плюсов не стали тащить. И, да, friend-ы в C++ тоже критикую в статье, если заметили. Но не только критикую ("критикуя - предлагай"), но и предлагаю, имхо, более адекватный вариант.

Oceanshiver
03.11.2025 14:11К сожалению, статью дочитать не смог - устал продираться через все эти шуточки-прибауточки

kemsky
03.11.2025 14:11Текст имеет все признаки гпт (да и код), читать невозможно.

VadimLL Автор
03.11.2025 14:11Можно конкретно написать, какие признаки GPT? Так же вместо того, чтобы приписывать невесть что, можно ведь просто спросить (именно это будет этично). И если бы меня спросили ответил: Текст писал ИСКЛЮЧИТЕЛЬНО сам. Разве не видно, что он имеет индивидуальные черты — мышление у меня поливалентное, не одномерное, со множеством ассоциаций, думал люди оценят, улыбнутся. Разве так пишет GPT? И опять, считаю что без конкретики — это как, вот вам ассоциация, если понравится: вброс сферического г..(галоши?) в вакуум (а раз вентилятор в вакууме не функционален, то точно галоши).
Так же как и код писал «вот этими вот руками». Заглядывал за некоторыми деталями в доки и в google конечно (а у него, конечно, gemini в первой строчке), ибо все детали того же Roslyn помнить невозможно (разве только на нем узко специализироваться 10 лет подряд). Никаких же промптов типа "напиши мне целиком такой-то ... проект",... готово, выкладываю — и в помине не было (хотя даже не знаю, хорошо это или плохо, скорее плохо).
И какой код? Примеры в статье? (вот их точно абсолютно никуда не глядя писал). В репозитории? Если там, то что конкретно там не так?Почему люди не пишут о самом материале статьи, вместо этого практически переходя на личности?
Посмотрел вашу ссылку.
Какой-то там куций функционал. Только для класса, только для internal, а может и только для static - непонятно из их единственного примера и описания:
https://github.com/piotrstenke/Durian?tab=readme-ov-file#friendclassВ то время как для классов — это как раз наименее интересный случай. Там имеем похожие недостатки что и в плюсах, о которых писал («эксгибиционизм» членов класса).
И главное — не музыкально. При написании кода Элвиса не напеть.
P.S. А у вас статьи, кстати, интересные.

posledam
03.11.2025 14:11Я честно пытался...

VadimLL Автор
03.11.2025 14:11Эх, может хоть кто скажет, где именно трудности?
Вот прям уже «Фигаро тут, Фигаро там» (или как сапер — «одна нога здесь, другая там»), разрываюсь, пытаюсь всем угодить, на каждый коммент максимально развернуто ответить, пытаюсь понять по очень скупым туманным фразам, что человек имеет ввиду. Вас, дорогие коллеги, похоже мне (конечно только мне) понять еще труднее.
Где? В вводной части? Обычно такие, лирико-хвилософские вводные части в книжках быстро «проглатываются».
Может в той, где сравнивается C# и C++? Ну она, как писал, вбоквел, только для тех Матроскиных, кто хорошо знает оба языка, ее можно без всякого ущерба пропустить.
Хотя, пожалуй, для понимания некоторых замечаний в других частях, все же надо представлять что такое friend-ы в плюсах. Может надо было дать вначале краткую справку? Если в этом дело, только скажите — добавлю.
В основной, технической части?
Не верится, что фрагменты с юмором мешают, их можно тоже пропускать без ущерба.
Сам пример, что сквозным образом проходит через всю статью? Разве он не элементарен?
Думаю, скорее всего что-то в самом техническом контенте.
Что именно? Какая фраза/абзац вызывают затруднения, требуют дополнительного пояснения? Можно несколько примеров?Когда оформлял статью, думал какой уровень сложность ей поставить, колебался между "Простой" и "Средний". Может тут ошибся и надо было ставить "Сложный"?

posledam
03.11.2025 14:11ИМХО. Чрезвычайно приторная, перенасыщенная над-юмором статья, переизбыток жеманства, пословиц, поговорок, отсылок и аналогий на предложение. Много воды, ребёнка надо ещё поискать.
И пусть, с одной стороны, у меня есть 100 рублей (св-во Rubles).
А с другой, ведь «не имей сто рублей, а имей сто друзей», а также у друзей ведь все должно быть «на лапопам», не так ли? Так что готов разделить с ним рубли. Хочу дать другу «ключи от квартиры где деньги лежат» — приходи дорогой, бери половину рублей из тумбочки.Можно брать любой кусок статьи, хоть вот этот. Если к каждому слову торопиться рифмовать подвернувшуюся пословицу, да ещё и присказку в придачу, то лучше не становится. Отнюдь. Какие ключи? Какие рубли? Какие квартиры? Какие тумбочки? Аааа ))))
Когда оформлял статью, думал какой уровень сложность ей поставить, колебался между "Простой" и "Средний". Может тут ошибся и надо было ставить "Сложный"?
Тема простая. Но лёгким чтивом это не назовёшь. Моментально теряешь смысл в потоке избыточной информации. Хорошо, что есть нейронки и можно получить пересказ :)

VadimLL Автор
03.11.2025 14:11Это поясняемая цитатами и пословицами — доказательная логика: почему число 100, а не любое другое как могли бы недоумевать, и почему надо делится с другом, и почему именно «на лапопам».
Чтобы не было вопросов что тут за цифры и действия такие взятые с потолка или с бодуна. Так сказать описание предметной области, которая будет моделироваться программой. Ну и постарался сделать это не скучно. А без цитат, ссылок-отсылок в серьезных изданиях так вообще не примут статью :)По идее такие описания должны восприниматься влет, легко и приятно, как хорошо знакомые и ярко-выпуклые образы, легкими мазками рисуя в воображении читателя живую «картину маслом» (эх, вот опять не удержался от цитаты). Если же спрашиваете: какие ключи, рубли, квартиры, тумбочки. То значит можно сделать вывод, что не находитесь в классическом русском контексте пословиц, мемов, литературной и кино-классики. Вот с этим ожиданием от читателя возможно и промахнулся. Переоценил народ в этом плане.
Хотя это больше про небольшие вводную часть и заключение (как уже говорил), в которых традиционно вполне допускаются лирические ноты. Основная же часть должным образом (как многие требуют) скучная. Ну а редкие вкрапления юмора... так в чем проблема просто пропустить их, если этот юмор не понятен? (ну или у меня он такой не смешной или слишком требовательный к читателю, эх, а ведь старался веселить, т.е. вызывать положительные эмоции).
Честно говоря даже верится что вот это было непреодолимое препятствие, имхо, даже одних примеров кода, которыми постарался обильно снабдить статью, достаточно. Больше похоже на то, что некоторые просто не поняли (поленились) материал статьи, об этом говорят и некоторые из их постов (развернуто отвечал на их все).

posledam
03.11.2025 14:11Вам дали пересоленную еду. Вы говорите: невозможно есть, она очень-очень солёная, соль даже скрипит на зубах. Но вам отвечают: вы просто глупый, ленивый, поверхностный, не разбираетесь в кулинарии, и вообще вот вам две, даже три часовых лекций на тему, почему вы должны поверить, что это шедевр.
Научитесь, наконец, принимать критику, как взрослый человек. Ваши кирпичи текста в комментариях не добавляют ясности, а только ещё больше усугубляют. Вы не обязаны с каждым соглашаться, но обвинять окружающих в лени, тупости, посредственности только потому что не оценили ваших художеств — хамство, не иначе.

VadimLL Автор
03.11.2025 14:11Спасибо за ваше мнение, оно очень ценно для меня!
И после этого вы говорите, что не воспринимаю критику? :)Уважаемый, очень взрослый человек, стесняюсь спросить: а на критику можно отвечать, просить аргументировать? Или только в духе «ему ссут в глаза — а он божья росса». Ибо как можете заметить она тут очень лаконична, без аргументации, порой просто фразой-эмоцией, и если бы она была применена к вам, то возможно (возможно, не настаиваю конечно), то вы бы ее, наверное (наверное) как раз и назвали детским садом.
Как же мощно вы меня припечатали, просто намотали на гусеницы: «вам отвечают: вы просто глупый, ленивый, поверхностный, не разбираетесь в кулинарии».
Позвольте, ну не совсем все же верно (хотя иногда и бывает что туплю) и это не мое мнение, «у меня и справка есть» (и совсем не об окончании «кулинарного техникума» и совсем даже не на двойки, а с точность наоборот).«Ленивый»? (хм, труд проделал огромный — реально, + тут, стараюсь, каждому очень-очень развернуто отвечаю, задаю вопросы...). Признали щедевром? Где на такое надеялся? Пожалуйста, прошу, не надо за меня выдумывать, ведь это справедливо считается очень не этичным. В таких случаях просто спросите меня, если хотите знать что реально думаю (а не что вы думаете, что я думаю, что я думаю).
Да и отвечают не все как вы, есть тут совершенно другие мнения.
Замете, остаюсь с вам в рамках вежливого общения, несмотря на откровенное хамство.
А теперь можете ставить свой очередной минус (ну если захотите, конечно, ведь не вежливо указывать другим что им делать, так что, конечно, не настаиваю).

posledam
03.11.2025 14:11Уважаемый, мне тяжело было читать вашу статью. Мне очень не понравился ваш стиль. Читать было неприятно. И еще более неприятно читать ваши комментарии, ваши нелепые ужимки, детские оправдания, и глупые попытки переложить с больной головы на здоровую. Я первым словом в ответ вставил "ИМХО". Понимаете, что это значит? И я уже окончательно убедился, что у вас какие-то серьёзные проблемы с самооценкой. Особенно потоки приторной лести и лизоблюдства в ответ на редкую похвалу. Испанский стыд. Успокойтесь пожалуйста.

VadimLL Автор
03.11.2025 14:11полная цитата вашего поста на который ответил:
Вам дали пересоленную еду. Вы говорите: невозможно есть, она очень-очень солёная, соль даже скрипит на зубах. Но вам отвечают: вы просто глупый, ленивый, поверхностный, не разбираетесь в кулинарии, и вообще вот вам две, даже три часовых лекций на тему, почему вы должны поверить, что это шедевр.
Научитесь, наконец, принимать критику, как взрослый человек. Ваши кирпичи текста в комментариях не добавляют ясности, а только ещё больше усугубляют. Вы не обязаны с каждым соглашаться, но обвинять окружающих в лени, тупости, посредственности только потому что не оценили ваших художеств — хамство, не иначе.
Где "ИМХО" то? Или не только у меня проблемы?
И вам что завидно, что на вежливый положительный ответ — вежливо положительно ответил? (если так, то, пожалуйста, не надо, читал, что согласно статистики, зависть — основная причина депрессий в США, поберегите себя).
Впрочем и вам и всем вежливо отвечаю (так что, еще раз, пожалуйста, не приписываете мне своих мыслей). Более того, даже вам вот ниже постом немного отлизаблюдил (в вашей терминологии).
Также благодарю вас, что отслеживаете и собираете библиографию моих постов (вот еще раз вам отлизаблюдил). Не хотите стать моим летописцем?
Коллега, а вы не находите, что вы мне тут столько всего наговорили, столько своих непрошенных оценок навешали, что тут можно долго разбирать всякие ваши issues, давая встречные оценки (может даже «букетик» issues? как это принято мягко называть в английском, ведь у них всегда «no problem», это только у меня по вашим словам серъзные problems). Но нет, так никак не могу, ведь как и все воспитанные люди, понимаю, что без запроса от собеседника самому давать ему оценку крайне не этично. В свою очередь, извините, если где-то невольно что-то проскочило.
И вот что мне странно: у меня есть сейчас время (ведь ~ «ленивые часов не наблюдают»), и я с удовольствием общаюсь с вами (получаю «роскошь человеческого общения»). Вдруг меня (глупого конечно), умные и взрослые дяди научат не только плохому, но и чему-то хорошему (и да, есть уже результаты/выводы, спасибо всем!). А вам вот все не нравится, но вы упорно продолжаете общаться (на кактусах тренировались?). Все-таки какие мы все разные и как же это интересно!

posledam
03.11.2025 14:11Очередной кирпич текста с оправданиями и наездами ) сказка о потерявшемся ИМХО )

VadimLL Автор
03.11.2025 14:11Ну, да, извините, что уделяю вам свое время, внимательно читаю ваши посты и стараюсь максимально полно ответить вам. Пожалуйста, не будьте к себе строги, уверяю, «ведь вы этого достойны!». Так что какие наезды? Извинения и благодарности. Хотите знать что такое наезды? Тут далеко ходить не надо — есть, есть тут характерные посты, можете перечитать на досуге, если забыли.
«ИМХО», конечно было в одном вашем хорошем посте, на который, имхо, хорошо ответил.
Только отвечали то вы мне, а потом я вам на содержание другого поста, гораздо ниже. Потеряли контекст? Тот ваш пост, который уже, как бы это помягче сказать чтобы вас не обидеть... Ай, не буду говорить, не хочу вас обижать, дорогой коллега.А что с оправданиями не так? Не имею право отвечать? А кто тогда защит меня бедного детсадовца от взрослых и таких невообразимо умных дядей?
Даже вот очень известная фраза гласит: «...Вы имеете право на адвоката. Если вы не можете позволить себе адвоката, он будет назначен вам бесплатно».
Тогда требую бесплатного адвоката! Пусть он меня оправдывает, а я буду «хранить молчание».

posledam
03.11.2025 14:11Вчера, понимаешь ли, утро было как обычно — солнце светит, холодильник пуст, носки разные, но сердце просит приключений. Думаю: «Надо выйти в люди. Пусть хотя бы они видят, что я ещё жив». Вышел из дома, ветер дует, голуби обсуждают политику, а я иду, как человек без цели, но с надеждой.
И тут — бах! — кофейня. Новая, блестящая, как freshly installed Windows, только без синих экранов. Вывеска сияет: «Кофе & Смысл жизни». Ну, думаю, если не тут, то где?
Захожу. Запах такой, будто ангелы жарят бобы на облаках. За стойкой — бариста, лицо у него как у человека, который знает все тайны мироздания, но делится только за доплату 50 рублей. Спрашивает:
— Что будете?
А я растерялся. Мозг крутит варианты, как старый принтер. И вдруг из меня вырывается:
— Латте…
Почему латте? Не знаю. Может, потому что звучит, как имя итальянского философа.Бариста кивает. Машина шипит, парит, свистит — будто паровоз на пути в Город Удовольствия. Я стою, нюхаю воздух, и вспоминаю детство, деревню, корову по кличке Зорька и как бабушка говорила: «Пей молоко — вырастешь большим, а кофе — это для тех, кто уже вырос, но жалеет об этом».
Через минуту он ставит передо мной чашку — белоснежная, с рисунком в виде сердечка, или, может, лёгкого разочарования — я не уверен. Пенку можно было бы использовать как строительный материал — плотная, как аргументы тёщи.
Делаю глоток.
И всё.
Мир перевернулся.
Я понял, что все философы ошибались — смысл не в бытии, а в балансе между молоком и кофе. Это как если бы Будда и Эспрессо устроили совместный стартап.Вкус — божественный! Мягкий, как комплимент от налоговой. Нежный, как кошка, которая впервые признала твоё существование. И бодрит так, что даже мысли начинают бегать по кругу, устраивая утреннюю зарядку.
Сижу, пью, размышляю: вот ведь жизнь — идёшь просто прогуляться, а оказываешься в кружке с молочной пенкой. Народная мудрость не зря говорит: «Без кофе и печаль не та, и радость подозрительная».
Вокруг люди с ноутбуками, каждый делает вид, что пишет роман. Один парень так напряжённо смотрит в экран, будто там прячется его зарплата. Девушка рядом фотографирует свой макиато под углом 45°, чтобы лайки сами ставились. А я — я просто открываю новые горизонты вкуса и смысла.
Выходил я из кофейни уже другим человеком. Воздух стал чище, улицы — философичнее, а в голове вертелась новая пословица:
«Кто латте не пил, тот жизни не видел. Кто пил — тот теперь знает слишком много».
И вот и думай теперь, зачем я вообще туда пошёл. Может, судьба привела, может — маркетинг. Но если резюмировать (для тех, кто не осилил 18 абзацев):
Вчера я пошёл в кофейню, решил впервые попробовать латте — и оказалось, что это чертовски вкусно.
Табличка «сарказм» нужна? Есть определённые места, где можно поражать воображение людей своей беллетристикой. Дзен, например.

VadimLL Автор
03.11.2025 14:11Некоторые образы понравились, реально хороши (не ai случайно? просто похоже). Единственное, уж очень, очень, имхо, гипертрофировали в попытке сарказма.
Чашка кофе с ложной сахара и чашка сахара с ложной кофе - чувствует разницу?Но не настаиваю, не осуждаю «вы художник — вы так видите».
У меня свое мнение, у вас свое. Или считаете это ненормальным?
posledam
03.11.2025 14:11Я считаю, что в первую очередь на данном ресурсе ценностью является техническая часть, а не художественная. Писать лаконично и по делу — искусство. Здесь я прихожу образовываться, получать знания, учиться на чужом опыте, делиться мнением, участвовать в обсуждении технических аспектов и деталей. Обилие шутеечек и не в меру художественный обвес только всё портят.

VadimLL Автор
03.11.2025 14:11Так давайте и обсуждать техническую часть, а не художественную. Рассчитывал, что именно-именно так и будет. Только и только «за» и руками и ногами.
Вместо этого в основном переход на личности (причем «заметьте, не я это предложил» — такую тему, или мне стоит быть польщенным к такому вниманию к моей личности?).
Да, есть у меня свои индивидуальные особенности. И народ за них зацепился (а мне вот не нравиться как мой сосед одевается, но тем не менее хорошо общаюсь с ним, никогда не поднимая тему его одежды). Их можно либо попытаться понять (и даже вдруг тут открыть для себя что-то новое, моим друзьям/знакомым — нравится), либо абстрагироваться от них. Тем более в технической части статьи этого немного. Ну либо, на самый худой конец, вот так как сейчас... хорошее тут слово было в постах, а вот, нашлось — «наезжать» (можно позаимствую?). Тоже вариант и ваше право.
Т.е. что же это получается? По факту многие люди здесь показали, что им гораздо важнее художественная, а не техническая часть? Одежка, а не внутренность?
Знал бы что будет такая неожиданная реакция — конечно по другому бы написал, для вас дорогие коллеги, сломал бы себя (пусть и поется в песне «Но не смотри, не смотри ты по сторонам, Оставайся такой, как есть, Оставайся сама собой...»).
Это первая (и похоже последняя) моя статья, о чем писал в самом начале и просил «Пожалуйста, позаботьтесь обо мне».
Что же, так вот она какая забота («вот он какой северный олень»), Ваше право.P.S. И может быть если бы вы чуть лучше разбирались в людях (простите, если ошибаюсь) то знали бы что часто люди шутят не от хорошей жизни. Жизнь боль и не только своя, но и близких. А юмор он помогает держаться. Если у вас все прекрасно, то реально рад за вас (всегда молюсь чтобы Господь и дальше хранил счастливого человека которого вижу).

vladislaw2020
03.11.2025 14:11Классная статья! Честно, не понимаю, что людям не нравится, я хоть и никогда не работал с C#(плюсы only), но все отлично понял, подача материала годная, читать приятно. Респект автору в общем)

VadimLL Автор
03.11.2025 14:11Спасибо Огромное! Ваш комментарий особенно ценен на фоне превалирующего непонимания.
Почитав иные комменты, считаю, что люди просто не поняли материал статьи.А некоторые, похоже, даже не потрудились дочитать (но осуждают). Особенно первые 2 мгновенных минуса, сразу, в ту же минуту, после публикации. Возможно это были северо-корейцы, которые прочитали лишь первый абзац со ссылкой на цикл о Южной Кореи (но ведь тоже не потрудились прочитать цикл, ибо там совсем не воспеваются порядки южан).
Написал, имхо, полезный инструмент, выложил в открытый доступ, берите пользуйтесь на здоровье, если кому понадобится.
Статью с любовью запилил, стараясь ее сделать не скучной, чтобы люди не засыпали на бубнеже о технических деталях, разбавил шутками юмора.
Хотя это больше про небольшие вводную часть и заключение, в которых традиционно вполне допускаются лирические ноты. Основная же часть, имхо, должным образом (как многие требуют) скучная.Даже не ожидал, что это будет такая огромная работа (причем все «безвозмездно, то есть даром» от чистого сердца).
И, думаю, конечно, тут каждому будет по человечески обидно.
Сижу-гадаю: «что же вам, собаки, еще надо-то?!»
Ладно, извиняюсь (пред теми кто отнес на свой счет, не понял юмора, цитаты) — собаки зачеркиваю, это я сгоряча, на эмоциях. Хотя «собака — друг человека», так что вполне в рамках тематики статьи :)Некоторые вообще за рамки элементарной этики выходят (поплачусь в жилетку):
https://habr.com/ru/articles/961264/comments/#comment_29059274Настолько в когнитивном диссонансе от «ожидание и реальность», что, думаю, уже никогда здесь больше не решусь написать что-либо еще (хотя идей достаточно).
Так что, еще раз, моя вам искренняя благодарность за понимание, умный, образованный, с хорошим чувством юмора человек.
DanielKross
Тема интересная, но статья попахивает графоманством. Имхо поменьше "шуточек" только пошло бы на пользу. Ничего личного!
VadimLL Автор
Спасибо за ваше мнение! Имхо, шуточек совсем немного (в общей доле всего материала, пожалуй, где-то сотые доли или меньше). Также, имхо, всегда приветствовалась живая подача материала, с юмором, который расслабляет, настраивает на игривый лад (а известно, что в не напряженной, игровой форме лучше всего идет усвоение). Совсем сухо - это справочник читать.
В конечном счете, ~ «вам без шушочек или ехать» :)
ZSN_2
Усвоение пива с сухариками...
VadimLL Автор
:) Нормальная шутка. Только странные люди - сами шутят, а другим запрещают.