Как стать автором
Обновить

Комментарии 63

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

public class AuthorizeService(UserRepository repository, 
                              PasswordHasher<User> hasher)
{
    //private readonly UserRepository _users = repository; // We don't need this!
    //private readonly PasswordHasher<User> _hasher = hasher;

    // ....
}

Я как-то больше ждал что-нибудь типа создания новых объектов по прототипу через with для стандартных классов, как это сделано у записей, чтобы не городить размашистые перекладывалки из пустого в порожнее из клиентской модели в бизнесовую, из бизнесовой в БД и не обмазываться мапперами там где не нужно

var growingDegreeDays = coolingDegreeDays with { BaseTemperature = 41 };

Либо в MVC каких-нибудь весёлых аттрибутов, позволяющих проконтролировать, что какой-то аттрибут в route совпадает с оным в модели

Спасибо за замечание! Могу поинтересоваться об источнике этого утверждения? Я как-то слишком консервативен чтобы даже помыслить, что объект может оперировать внутри себя не своей частью (this-то не применим), но возможно стоит осмыслить? :)

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

Что значит не своей частью? Это вполне себе field этого объекта. С невозможностью ткнуть this - это скорее всего баг имплементации конкретно этого сахара. Причём если имена осмысленные, а не везде одно и то же имя (но почему-то с разными значениями?), то this использовать и не приходится. В любом случае нет смысла дублировать приватные штуки свойствами - только перфоманс ухудшает.

С невозможностью ткнуть this - это скорее всего баг имплементации конкретно этого сахара

Боюсь что это буквально тот случай, когда не баг, а фича :) Из документации:

Primary constructor parameters aren't members of the class. For example, a primary constructor parameter named param can't be accessed as this.param.

Собственно, когда я писал о том, что this не является частью класса - как раз и поминал этот отрывок. Помимо собственных консервативных убеждений x)

В любом случае нет смысла дублировать приватные штуки свойствами - только перфоманс ухудшает.

Обращаясь туда же:

Primary constructor parameters may not be stored if they aren't needed.

Перепроверил на rc2 - никакого дублирования нет, если аргумент нигде за пределами инициализации не захватывается.

Очень странно написано, вот же они)

Не мемберы - это когда они где-то снаружи. А тут они только изнутри и доступны, как и подобает приватным филдам по канону)

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

“никакого дублирования нет, если аргумент за пределами инициализации не захватывается”

В вашем примере захватывается. Так что, противоречия нет.

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

Я отвечал вашему оппоненту.

Отторжение "сахара" стало уже притчей во языцех.

Но давайте посмотрим на следующие тезисы:

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

Отсюда, неприятие различных var, let и еже с ними, в Java до сих пор с этим борются.

  1. Всё, что может делать машина, а не человек -- должна делать машина. Поощрение обезьяньего труда я не понимаю. Если можно написать меньше букв с тем же результатом, с учётом продвинутых IDE, то нужно к этому стремиться.

  2. Консервативные языки программирования, в которые разработчики бояться добавить какой-либо сахарок, вытесняются молодыми и прогрессивными.

Расширить синтаксис, чтобы не пострадало тонна легаси кода, задача отнюдь не тривиальная. Приходится идти на жертвы, иначе как грибы вылезут унылые статьи "это было последней каплей! уйду на PHP/Python/etc." :)

Вы правильно пишете, что всё это "тезисы", и соглашаться с ними вовсе необязательно. Разумный объём сахара полезен, но конкретно в случае C# мы уже вышли за все рамки. Если функцию можно описать 100500 способами, то явно в консерватории что-то не то.

Соответственно, примеры "консервативных языков", которые были вытеснены именно по причине консерватизма, наверно, можно пересчитать по пальцам одной руки. Язык изучают для того, чтобы с его помощью что-то создавать. А тут получается как с апдейтом Thunderbird: не радуешься новому, а думаешь, чего они сломали на сей раз, и как с этим жить. Даже если эти фичи лично мне не нужны, всё равно приходится разбираться хотя бы на уровне "чтения со словарём".

«Есть всего два типа языков программирования: те, на которые люди всё время ругаются, и те, которые никто не использует.»

Bjarne Stroustrup.

Если ваш тимлид считает C# сахарным, то что с ним случится, когда он увидит kotlkin или ts например? Сахарный диабет 2 типа?

Интересно, спасибо. Расскажите, пожалуйста, как это поможет упростить работу разработчикам?

Давайте попробуем суммировать самое важное, хотя, кажется, я пытался осветить это в статье :)

  1. Первичные конструкторы - синтаксический сахар, позволяющий уменьшить визуальный шум при инъекциях зависимостей

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

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

  4. Inline массивы и перехватчики - непрямая помощь разработчикам, путём улучшения качества (оптимизации inline массивами) и расширения функционала (перехватчики кода) библиотек.

Первичный конструктор в виде пропозала появился к C#6, но вошёл только в 12.

Перехватчики нужны для развития source generators.

Перехватчики выглядят заманчиво, но привязывать их через файл и номер строки - это скорее для програм работающих с кодом, а не для реальной жизни. А то Enter поставил и "привет".
Можно ли через перехватчик вызвать код, который перехватили? (сделать обвертку)

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

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

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

Первичный конструктор это жесть какая-то, ощущение что фичи делаются просто чтобы сделать, а не действительно что-то полезное

Не знаю как у вас, но у меня уже глаз начал дёргаться, когда надо три раза подряд написать имя зависимости, 2 из которых дублируя ещё и тип (помощь от Visual Studio помогает, но полностью не спасает). Так что я всё же рад новой возможности - глазу теперь можно дёргаться на один раз меньше X)

Наконец-таки можно class Foo { List<Bar> Bars { get; } }
new Foo { Bars = [new Bar{...}, new Bar{...}] };

НЛО прилетело и опубликовало эту надпись здесь

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

НЛО прилетело и опубликовало эту надпись здесь

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

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

Немного проблематично поддержать версию языка, которая ещё не вышла :(

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

Частный случай поведения обычно бывает плохим решением.

Но ведь аргументы метода, коим является конструктор, не есть поля класса. Это никак не решить, не ломая парадигму ЯП. Это придётся весь синтаксис перетрясти, а также атрибуты, так можно всё сломать.

Спасибо, аж захотелось что-нибудь на сишарпе сварганить, лет 5-6 его уже не трогал😁 хотя страшно окунаться в совсем забытую да и скорее всего уже совсем другую экосистему 🥺

На мой взгляд - самое время! Многие выражают скепсис по поводу нововведений (да и я немного этому способствую) ), но по-моему с каждым обновлением язык становится всё приятнее для использования. А учитывая что как раз 6 лет как .NET стал работать под Linux - оправданий использовать что-то ещё исчезающе мало :)

А в чём преимущество именованных через using кортежей перед обычным типом?

Присутствие кортежей это само по себе code smell. Но там, где они всё-таки нужны (т.е. где совершенно точно не нужна инкапсуляция) - позволит улучшить читаемость, сохраняя удобство.

Не понимаю, а можете ли показать пример где

using Vector2 = (double X, double Y);

будет лучше, чем

record /*struct*/ Vector2(double X, double Y);?

Кортежи обычно имеют смысл на одноразовое что-то, а если ты создаёшь алиас, то значит используешь её в разных местах, и тогда имеет смысл написать отдельный тип.

На мой взгляд, ответ в вашем же комментарии:

Кортежи обычно имеют смысл на одноразовое что-то

Только "одноразовое" стоит воспринимать в контексте одного класса (файла). В таком формате я бы отдал предпочтение кортежу - его семантика однозначно показывает что это bag of data и ничего больше, в то время как к структурам и записям могут быть вопросы (глобальный using сразу выносим за скобки).
Но это, на самом деле, вкусовщина, просто раньше ограничение на using alias было, из-за чего приходилось выкручиваться вышеописанным образом, а теперь можно действовать напрямую.

Вот ещё бы сделали эти using-и с в любом скоупе - не только файле - было бы совсем хорошо...

Если вы используете стороннюю библиотеку, в которой такие структуры не определены, а вам хочется свой код сделать более лаконичным и наглядным - то использование алиасов позволит это сделать без введения доп. типов, конвертации, нагрузки на GC и т.п. Понятно, что этот вариант использования является довольно узким, и без него вполне можно же жить, ну так это вообще ко всем алиасам относится (тот же `using static ...` всего лишь позволяет чуточку меньше символов писать. Мелкая, иногда приятная, но точно не необходимая "плюшка")

в чём преимущество именованных через using кортежей

В упрощённом создании:

return (1,2);

вместо

return new Vector2(1,2);

Если возвращаемый тип известен, то можно

return new(1, 2);

inline массивы реализованы как-то прям сверх костыльно. Да и поддержка синтаксиса выражений коллекций через атрибуты для кастомных классов тоже выглядит костылём, что мешало запилить обыкновенный интерфейс отдельного класса-билдера + дженерик интерфейс на сам класс коллекции?

Вообще список изменений выглядит так себе. Да есть приятные мелочи (которые давно должны были быть сделаны), но где DU? Похоже не дождемся?

Что такое DU?

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

Пока в качестве DU можно применять наследование, или generic-кортежи.

Безусловно скорее всего есть какие-то подводные камни и сложности, учитывая большую кодовую базу языка, возраст и количество уже реализованных фич. Но производительность? Честно говоря, не совсем понимаю, что вы имеете в виду и как она мешает реализовать DU. DU в ООП языках это базовый класс и наследники. И какое-то ключевое слово или атрибут позволяющий указать, что это DU. Плюс exhaustiveness в свитч. И как бы все. Причем здесь производительность не понимаю. Классы и наследование уже давным-давно реализовано. Добавить ключевое слово или атрибут, так же не супер сложная задача. Ну и проверка на полноту тоже не рокет саенс.

только какой-нибудь скриптовый ЯП

Спорно. В том же Dart-е реализовали. И возможно, когда-нибудь и в C# реализуют.

приблуда синтаксиса поверх хеш-массива

А причем здесь хеш-массивы?

производительность? Честно говоря, не совсем понимаю, что вы имеете в виду и как она мешает реализовать DU

От DU ожидают, что в зависимости от дискриминанта, в одной и той же локации может лежать как managed-ссылка, так и value. Потребуется доработка GC.

Вижу, что сделано через наследование, варианты наследуются от базового пустого класса. То есть, нельзя сделать union { int, double }, не превратив его в ссылочный тип. Не бесплатно в перфомансе. Мне кажется, такие "юнионы" уже довольно легко использовать в современном c# через pattern matching.

Мне кажется, такие "юнионы" уже довольно легко использовать в современном c# через pattern matching.

Кроме проверки полноты - да.

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

Выше уже обсуждали, но в целом не совсем. Технически - да, поле есть, но ситуация тут не как с записями - this к нему не применишь, в отладчике не посмотришь - только рефлексия.

С таким количеством изменений и новых конструкций, которые возникли в последние 5 лет, пора уже каждой команде писать Coding standard о том, какие конструкции они используют, а какие категорически нет :-D. Я ещё не оправилась от дефолтных методов в интерфейсах, а они уже конструктор в имя класса запихнули :D.

Чтобы оставаться на месте, надо бежать изо всех ног.

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

Давайте посмотрим на Go лет хотя бы через 10.

И самому go, кстати, не так много лет. Всё-таки C# со своим возрастом и иногда немного неуклюжим видом вполне заслуживает периодического омоложения. Как и, возможно, go в будущем.

Омоложение и стремление за другими языками - конечно да, но можно делать лаконично и по значимой ценности. Но часто происходит засахаривание там, где нужно и там, где нет. Понятно, что можно сказать была бы возможность, а применение найдется. С другой стороны, очень многие возможности можно реализовать как отдельные пакеты и тогда команда сама будет себя ограничивать или не ограничивать (тут смысл 1) в стремлении к единообразию кода до его написания и без массивных кодинг стандартов, а не только средствами анализаторов, линта, код ревью, когда код уже написан; и 2) уменьшении желания новобранцев "самородков" делать/писать так, потому что модно, а не потому что нужно - необоснованное применение или внесение нового подхода без согласия команды; когда возможность есть и ставить ничего не надо - то часто даже мысли не возникает о причиняемых последствиях) . Ну вот например, все что связано с кодогенерацией - почему бы отдельным пакетом не сделать, ведь они почти в каждой версии что-то для нее добавляют.

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

"Однако ещё более заманчиво звучат фреймворки для юнит-тестов – наконец-то можно будет перестать делать по интерфейсу на каждый класс, просто чтобы замокать его в тестах. " Telerik.JustMock в помощь

Зарегистрируйтесь на Хабре, чтобы оставить комментарий