Pull to refresh

Comments 52

У вас какая-то странная IDE. У меня подсвечивает (vscode)

Rider тоже такое не пропустил

Hidden text

Обмажутся своими блокнотами, а потом в ноги стреляют!

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

Недавно сталкивался с тем, что мне Idea 2023 ultimate (не знаю, что важнее из этих двух) подсвечивала ошибку, а коллеге в версии 2022 community — нет. Так что вполне можно быть, что и в новых версиях Райдера добавили проверок.

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

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

TreatWarningsAsErrors

Это директива компилятора, включается в файле проекта.

VS2022 подствечивает там потенциальную ошибку.

Может раньше не было правда.

Спасибо, что протестировали это в своем IDE. В данной ситуации использовался Rider. Подробнее написал тут: https://habr.com/ru/articles/764586/#comment_26015534
Очень круто что сейчас ошибки подсвечиваются.

Вроде все логично: конструкция с фигурными скобками разворачивается в серию последовательных вызовов .Add() на коллекции, и нововведением это не назовешь - кажется, еще с версии C# 5.0 доступно. Иногда эта возможность действительно удобна, например когда поле является get-only:

class Foo {
    public List<int> A { get; set; } = new List<int>();
    public List<int> B { get; } = new List<int>();
}

var foo = new Foo
{
    A = new List<int> { 1, 2 }, // работает
    // B = new List<int> { 3, 4 } // не сработает - ошибка компиляции
    B = { 3, 4 } // сработает
};

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

Ну не знаю, в моем понимании ExList = { 1, 2 } это присваивание объекту некоего составного литерала, а никакие не Add. Так что создатели C# здесь сделали фигню. Добавление там new не изменяет семантики всего выражения - это по прежнему присваивание объекту другого объекта.

Если пытаться прочитать код на языке X, опираясь на интуицию и знание какого-то другого языка Y, то абсолютно любая фича языка X может показаться странной. Наверное, программисту на PHP покажется, что foo.bar - это конкатенация двух строк, а не обращение к методу, и так далее. Такой подход едва ли конструктивен.

В сишарпе добавление new меняет-таки семантику, потому что new { ... } - это выражение, и new () { ... } - это выражение, а вот просто { ... } - нет. Его нельзя сохранить в переменную или передать в функцию. Соответственно, когда вы пишете справа от знака равно что-то, что не является выражением, это не может быть обычным присваиванием и должно быть чем-то еще - в данном случае, инициализацией коллекции через последовательные вызовы метода Add.

Если у вас есть идеи, как можно было бы описать инициализацию вложенной коллекции лучше - поделитесь, пожалуйста.

ExList += { 1, 2 } // add semantic

ExList = { 1, 2 } // assign semantic

Мне кажется как то получше чем то что есть. + это должно работать как то так для присваивания

ExList = { 1, 2 } =>

if ExList.IsNull () { ExList = new() { 1, 2 } }

else { ExList.Clear(); ExList = { 1, 2 } }

И как то так для добавления

if ExList.IsNull() { ExList = new() {1, 2 } }

else { ExList.Add(1); ExList(2); }

Я не очень представляю в каком случае будет иметь смысл НЕ инициализировать объект заданными значениями если он нулл, а кидать исключение как сейчас делает такой синтаксис, ну и явный += как то заметней чем разное поведение в зависимости от отсутвия наличия new ()

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

Если сделать так, то снова получается неконсистентность — при инициализации оператор += есть, а отдельно его нету. А если сделать его отдельно, то снова беда: один и тот же оператор вызывает то operator +, то метод .Add() в зависимости от того что справа. А сделать всё через один метод нельзя — потому что {1, 2} не литерал объекта, а просто синтаксис для инициализации коллекции.

если сделать его отдельно, то снова беда: один и тот же оператор вызывает то operator +, то метод .Add() в зависимости от того что справа.

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

снова беда: один и тот же оператор вызывает то operator +, то метод .Add() в зависимости от того что справа

возможно тут написано что-то на джававском, но в моем понимании одно должно вызывать другое… неужели в шарпах не так?

А сделать всё через один метод нельзя — потому что {1, 2} не литерал объекта, а просто синтаксис для инициализации коллекции.

Спасибо за полезный пример применения, он добавляет ясности. Да, действительно, это нельзя назвать нововведением, но в тот момент людей знающих этот синтаксис не оказалось рядом и для меня это было чем то новым.

Конструкция new() без указания типа вообще была введена как аналог var, но для R-value

То есть можно написать либо:

var l = new List<int>(){1, 2, 3};

либо

List<int> l = new(){1,2,3};

Но причём тут NRE? А всё дело в том, что{0, 1} заменяет конструкцию .Add() И вот эти два блока кода оказываются идентичными:

А это так вообще первый раз слышу. И IDE почему-то тоже не в курсе

VS2022 17.7.4
VS2022 17.7.4

Да, действительно, это не работает с обычным Add. Эта конструкция используется только при инициализации объекта. Выше подсказали хороший пример использования: https://habr.com/ru/articles/764586/#comment_26015170.

Вот именно из-за всего этого я ушёл с C#. Писал на нём с 2003 года. Помню как вышел .NET 1.1. Помню как было круто с дженериками в .NET 2.0.

Я прилежно учился и следил за всеми новыми выпусками .NET. Я помню тот день, когда Скотт Хансельман показал гитхаб для ASP.NET.

Я думал о том, как будет круто когда сам шарп превратился в по-настоящему открытый язык. Но нет. Это его просто убило в моих глазах.

Я вообще не понимаю, зачем надо было смешивать строго типизированный язык с Яваскриптом и чисто функциональными языками. Что не так с MyClass c = new MyClass()? Учитывая с тем, что всё на ватокомплите и после набора первых трёх быкв вижуал студия [и любая другая ИДЕ] могла всё автозаполнить. Неочевидно, но наличие var эту фитчу отключило. В любом случае, что ты будешь делать с сэкономленными фемтосекундами?

После того, как самый простой switch-case превратился в тьюнинг-полного монстра я ушёл в мир голанга.

Все эти свистоперделки и рюшечки превратили отличный язык в перл. Сейчас код на шарпах выглядит одинаково до и после шифрования файлов исходника.

думаю вы ушли на голанг потому, что за него больше платят

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

Примечательно, что безобидное высказывание вызвало у кого-то негатив :)

Работодатели всё-таки ищут не просто [профессиональных] разработчиков, а тех у кого есть опыт с определенным стеком. Нельзя написать в резюме "я решаю задачи бизнеса" и стать нарасхват.

Перекосы в разных стеках есть - не в разы конечно, но всё же C#/.NET никогда не славился большими ЗП.

С таким подходом вы не заработаете денег.

Здесь я бы не стеки новые учил, а масштабировал свои усилия для большего кол-ва результата -- шел в бизнес и т.п.

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

зачем надо было смешивать строго типизированный язык с Яваскриптом и чисто функциональными языками

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

Что не так с MyClass c = new MyClass()?

Да всё в порядке. Если визуальный шум вам не мешает, то пишите на здоровье. В стайлгайдах Microsoft принято в большинстве случаев указывать тип, если только он не очевиден по выражению справа, как у вас. Но многим людям это кажется избыточным, поэтому они используют `var`.

самый простой switch-case превратился в тьюнинг-полного монстра

Опять же, обычный switch-case остался как есть. Вам никто не запрещает писать так, как вы писали во времена 15 лет назад во времена .NET 2.0. Лично мне новый switch expression тоже не нравится, я считаю его избыточным и плохо вписывающимся в язык, поэтому я им практически никогда не пользуюсь. Портит ли его наличие язык в целом? Едва ли, хотя разработчики инструментария (Resharper, Rider, статических анализаторов и т.д.), которые вынуждены это поддерживать, наверняка со мной не согласятся.

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

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

С технической точки зрения все решаемо: поставьте минимальный langversion в проекте, настройте codestyle на свое усмотрение с помощью Stylecop, Resharper, Roslyn Analyzers - на здоровье. Останется только найти людей, которые согласятся в таких условиях работать

Дело в том, что если вы должны писать тону кода и развивать огромный проект, то switch expression позволяет писать то же самое, но быстрее - это здорово экономит время и превращает сотню if в элегантную конструкцию, и становится проще анализировать код, который занимает много места. В то же время бывают спорные решения, но благо развитие языка открыто и можно предлагать свои или обсуждать предложенные другими функции на github.

Мне не нравится, что switch expression служит той же цели, но отличается от switch statement практически во всем: ключевое слово switch пишется после выражения а не до, вместо default используется _, вместо case - стрелка =>, и самое важное - не проваливается на следующий вариант при отсутствии break.

Вместо этого можно было все унифицировать:

  • Писать switch всегда перед проверяемым выражением. Разделять на switch expression и switch statement по тому, располагается ли оно внутри выражения или нет.

  • Разрешить X => Y в switch statement как синоним case X: Y; break;, по аналогии с expression body в свойствах и функциях

  • Разрешить _ как синоним default

  • Не делать case > 1, а использовать более общий case var x when x > 1

Самое досадное, что в Java это сделали как надо, а в C# не смогли.

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

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

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

Какие-то смузи-стайл аргументы, уж извините. Я ушёл из C# потому что, всё не так, я думал оно вот так, а оно вон оно как, не так и не эдак. Зачем так надо было-то? То то, то это, не то ни это, монстр какой-то.

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

Когда в Go внезапно что-нибудь добавят, и там найдётся свой процент ненавистников "свистоперделок", которые уйдут на что-нибудь очередное хайповое. Ох уж эти свистоперделки, никакого спасу от них нет :)))))

Ничего оно не отключило. Если внимат

Имхо, во всех примерах проблема не с сахаром, а с кодом

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

Код абсолютно верный и рабочий когда коллекция существует.

var result = new ExampleClass
{
    ExString = "Тут не должно быть взрыва",
    ExList = { 0, 1 },
    ExString2 = "И тут",
    ExInt = 24
};

Смотрите, автор указал такой код.

Если я напишу без сахара, типа как

var result = new ExampleClass();
result.ExList.Add(0);
result.ExList.Add(1);

То он так же развалится, тут не в сахаре дело.

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

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

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

Но ведь и без сахара код выглядит совершенно нормально

Признаться, я не очень понимаю, к чему вы ведете

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

А вся история автора выглядит очень странно, начиная от отсутствия подсказок IDE и заканчивая

Всё началось с того момента, когда в нашем проекте начали "выстреливать" ошибки NRE.

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

Собственно, я и веду к тому, что автор на очень неудачном примере пытается показать, что сахар плохой. А плохой тут не сахар, а код, вот и всё.

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

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

Да, нужно включить везде nullable и настроить правильно IDE и компилятор, так чтобы при возникновении null где-либо - такой код не компилировался.

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

Единственное разбирать чужой код из сахара проблема.

Допустим мне бы в голову не могло прийти что такое вообще возможно на c#: (a, b) = (b, a). Это обмен значений между переменными, кто не в курсе.

Если точнее это создание кортежа и его декострукция.

Забавно, я думал, что только в питоне идут споры про приемлемость применения list comprehension и подобных сокращений/улучшений/ухудшений кода, а оказывается нет)

Синтаксический диабет. Извините.

Недостаточное покрытие тестами.

Да и статического анализатора как будто не завезли на проект...

Sign up to leave a comment.

Articles