Комментарии 152
В C# и .NET одновременно добавили нативные типы
Добавили?! Они же там с рождения были! Максимум, что могли добавить в язык — так это короткие псевдонимы и арифметические операции (которые и раньше были, но не языковые, а перегруженные).
Частичные классы в C# есть уже давно, их изначальная цель — отделять код, сгенерированный неким дизайнером от кода, написанного программистом. В C# 9.0 подогнали частичные методы.
[...] По имеющейся информации частичные методы будут тесно связаны с генераторами кода, где и предполагается их использование.
Частичные методы точно так же есть уже давно, и их именно для генераторов кода и делали. Их ещё дизайнер Linq2Sql использовал как возможные точки расширения...
Новые тут разве что out-параметры.
В нашей компании .NET используется с самого его рождения
Как вы относитесь к перенагромождению C# фичами? Помните, каким простым был C# 2? Ощущение, что фичи добавляются ради фич, чтобы имитировать деятельность. Напр., не вижу никакого смысла в новом обратном синтаксисе MyType t = new()
Или, например, кому было худо от отсутствия маппинга System.IntPtr как nint?
Сейчас мне больше импонирует подход Go, где каждое новое изменение языка/стандартной библиотеки тщательно выверяется ("а стоит ли оно того?")
Лично я отношусь с осторожным интересом.
Давайте по-чесноку: количество Go-разработчиков не сопоставимо с количеством .NET-разработчиков на C#. У последних и коммьюнити более активное, и задачи более разнообразные. Соответственно, требований к развитию языка у них больше.
Разработчики языка получают довольно много предложений по добавлению всяких фич, и далеко не все из них попадают в релиз. По разным причинам: от откровенно нелепых требований до нехватки ресурсов на реализацию разумных. Те фичи, которые попадают, иногда (что уж там) внутри выглядят довольно странным костылём, как, например, кортежи. Но это, считаю, не правило, а вынужденное исключение.
В C# 9.0 я одобряю и поддерживаю большинство нововведений. И если попробовать написать один и тот же код для современных задач на второй и хотя бы восьмой, актуальной на сегодня, версии C#, то в первом случае вы столкнётесь с огромным оверкодингом, плохой производительностью и трудной поддержкой созданного решения. Всё-таки, язык ушёл далеко вперёд вместе с платформой.
Ну с C# 2.0 разница может будет большая, а вот с 5.0 уже не особо. Как раз с тех пор глобально ничего супер-полезного не добавили. Хотя полезные мелочи тут и там, конечно, используются. Но прям удобств всё не добавляют и не добавляют. разве что генераторы чуть более решительные шаг вперед, но про них ниже.
Напр., не вижу никакого смысла в новом обратном синтаксисе MyType t = new()
А так:
var x = new List<Point>{ new (10, 11), new (4, 66)};
private List<T> items = new();
Ну и в довесок — а свойства с означенным значением, или как оно по русски называется: public List<T> Items {get;set;} = new();
Да, попробуйте сами на https://sharplab.io/
Чёрт. Какая прекрасная фича. В этом релизе они добавили почти всё, что мне так не хватает в повседневной работе с СиШарп. Особенно рекорды и инит
Рекорды — это просто имба, действительно очень сильно упрощающая жизнь фича для иммутабельных типов и функциональщины.
Мне в повседневной жизни не хватает АДТ (чтобы описать тип ИЛИ то ИЛИ это, реализация на свитче по типам выглядит ужасно неудобно), не хватает хотя бы базовых пруфов (хочу получить массив объектов, отсортированных именно вот по этому полю), тайпклассов (хочу вешать ограничения на типы, а не на инстансы типов. Например
void PrintMaxValueTwice<T>() where T : HasMaxValue
{
Console.WriteLine(T.MaxValue);
Console.WriteLine(T.MaxValue);
}
PrintMaxValueTwice<int>();
PrintMaxValueTwice<TimeSpan>();
Не хватает ХКТ, чтобы я мог абстрагироваться от конкретных типов в ненужных местах:
async T<int> DoubleValueAsync<T>(T<int> asyncFunc) where T<X> : IAwaitable<X>
{
var result = await asyncFunc();
return result * 2;
}
var t1 = DoubleValueAsync(Task.FromResult(10));
var t2 = DoubleValueAsync(ValueTask.FromResult(10));
Ну и рекорды, да, немного экономят на необходимости жать ctrl + alt + enter -> generate equality members.
Хороший аргумент. Но большое число вариантов сделать одно и то же, конечно, вгоняют в грусть.
До крестов шарпу пока далековато, хотя, признаться, новый синтаксис паттерн-матчинга инопланетный: использовать в шарпе "неполные" куски кода типа (< 0 or > 10)
— это не так же естественно, как в функциональных языках, где эти куски по отдельности реально существуют.
Все остальные фичи идут на упрощение восприятия того, что и так существовало: если у записи нет оператора равенства, то не надо думать о его семантике; если нет функции Main, то не надо думать, какой из вариантов функции использован; большинство будет использовать генераторы, а не писать их; target typing чисто убирает копипасту в нескольких местах, где она ещё оставалась.
Лучше бы они CoreRT до ума довели, а то оно до сих пор нормально нативный код сгенерить не может.
Прямо какой-то пролог получается
А где тут F#? Паттерн матчинг например был в хаскеле в 91м году, в скалакотлинах чуть позже, почему именно F#?
Из всего перечисленного конечно радуют ковариантные типы у виртуальных методов, адекватный вывод типа у ? :
тернарного оператора, ну и паттерн not
.
Недавно смотрел обсуждение с, если не изменяет память, Мадсом Торгерсеном (ведущий дизайнер языка), разговор был кажется с ЮАР-коммъюнити. Там самый популярный вопрос был — почему with
не завезли для кастомных типов. И ответ был очень интересный — они планируют ввести некий то ли аттрибут то ли еще какой-то флаг для методов, который будет указывать, что метод конструирует объект. Работает например для фабрик. Благодаря этому можно избавиться от внутренней магии, которая сейчас существует в record
, и реализовать адекватную поддержку with
для типов, ну и добавить немного оптимизаций. Я конечно разделяю энтузиазм полноценных DTO на классах с наследованием и вот этим всем, но условный struct record
, или даже ручной readonly struct
с get/init
свойствами и поддержкой with
очень сильно пойдет на пользу при работе с immutable типами, особенно сейчас, когда везде Span
, ref
, stackalloc
и вот это вот все. Можно будет заводить var otherRectangle = thisRectangle with {Width = 10}
. Придется ждать 10 версии языка.
Я бы сказал — в некий мультитул. К примеру, у меня есть такие физические устройства: от швейцарского (на самом деле китайского) ножа до мультиметра. И у них есть такие функции, которыми я ни разу не пользовался и даже не знаю, что это такое. Но это не мешает мне с их помощью решать свой круг задач: всегда иметь под рукой мини-отвёртку с мини-пассатижами и прозванивать электрические цепи. А это даже не половина того, на что они способны.
С языком то же самое. Это ни однозначно плохо, ни однозначно хорошо. Это потребность — иметь гибкий инструмент ценой некоторых компромиссов. Как было сказано мною выше, диапазон задач для C#/.NET огромен, а самое главное — они меняются. Соответственно обогащается и меняется и язык.
Рано или поздно развитие C# и .NET может дойти до какого-то критического порога, за которым появится… например, C# Lite и очередная инкарнация .NET Compact. А может мы пересядем все на другие компьютерные архитектуры, и C# с .NET плавно перекатятся туда, по пути соответственно изменившись и стряхнув с себя ненужные артефакты.
Так и развивается всё в природе. Это нормально.
Отсутствие необходимости писать полное название типа при перечислении элементов коллекции делает код чуть-чуть чище.
Здорово (как и всё остальное). Но немножко жалко что всё ещё приходится писать полное имя класса при определении конструкторов и деструкторов, вместо лаконичного this
(как в D):
class SomeClass {
this() {...}
~this() {...}
}
Вот совершенно непонятно зачем нужны для них полные имена. Хотя современные IDE позволяют на лету всё это рефакторить, но всё равно это кажется избыточным.
Или так:
class SomeClass {
new() {...}
}
в C# уже есть зарезервированное слово ctor.
И cctor для статического конструктора.
class SomeClass {
ctor() {...}
}
Насколько я помню, зарезервировано оно в IL, а не в C#. И в начале имени метода добавлена точка, как раз чтобы не на стороне языка не пришлось ничего резервировать.
Не знаю у кого отвалились контейнеры, а у нас отвалилась вся подсветка в студии и половину сборки приходилось менять чтобы заработало, и после каждого обновлении RC-версии студии (каждую неделю) чинилось что-то одно и отваливалось в трёх разных местах. А обновы ставить приходилось, потому что весь тулинг устаревал на глазах. Человек ушел в отпуск, пришел через 2 недели — уже csproj вместо project.json, bower/npm депрекейтед, теперь в моде снова нугет, ну и так далее… Собсна да, в какой-то момент для управления зависимостями использовалось сразу три пакетника: bower, npm и json. У нас решили проект как раз на нем делать, так что я прекрасно помню дату — весна 2016го, там как раз все три использовалось, потому что часть пакетов была только в одном, часть во втором, часть в третьем. И только великолепный jquery был подключен сразу во всех трех пакетниках, причем емнип несовместимых версий. Сидели писали код в блокноте, потому что через консоль все компилируется, но в ИДЕ всё красное, включая юзинги и любые типы включая String. Все ошибки компиляции вычленять нужно было из консоли, ошибки во вьюхах MVC — протыкивать руками все страницы и смотреть, рендерится ли правильно или нет. Да, весело было.
Но не думаю, что в этот раз так будет, все же технология стабилизирована, .Net 5.0 это исключителньо маркетинговое название, тот же кор что и был, просто заодно депрекейтят окончательно настольный фреймворк.
епрекейтят окончательно настольный фреймворкВсмысле?
Ну есть настольный, а есть кор. Пятый — это на самом деле кор, но с брендом от настольного фулл фреймворка. Такие вот чудеса маркетинга.
А ещё интересно что .Net Standard будет
The net5.0 TFM combines and replaces the netcoreapp and netstandard names. This TFM will generally only include technologies that work cross-platform, like was done with .NET Standard. However, if you plan to share code between .NET Framework, .NET Core, and .NET 5 workloads, you can do so by specifying netstandard2.0
Ссылка
Как обычно, решарпер подскажет что там нового подвезли
А что нужно? с 2016 пишу под линух, полет нормальный.
Просто перестал писать UI и переключился исключительно на написание приложений в докере. Если вдруг нужен гуй — то просто пишу его на реакте и он из шарпового кода таскает данные по хттп.
Если вдруг нужен гуй — то просто пишу его на реакте и он из шарпового кода таскает данные по хттп.
Может, это и удобно, но ресурсозатратно. В итоге приложения, которые раньше ели 10-20 мегабайт, теперь жрут больше гига из-за того, что тащат с собой браузер.
Ну я не предлагаю делать так какой-нибудь плеер или игру. А какой-нибудь постгрес или монго я и локально в докере запускаю — так удобнее, чем устанавливать. Оверхед там совсем небольшой.
Но в целом я просто про то, что гуй это очень нишевая вещь, наравне с каким-нибудь эмбедом и суперхайлоадом. Под него просто не заточены большинство технологий, и людям там приходится страдать.
Ну я бы не назвал это прямо страдать, но да гуй, особенно десктоп, это скорее вещь прикладная и как минимум в B2B она обычно делается постольку поскольку.
Ну я прям страдал. Когда хотел асинк-авейт, а мне такие "а у нас .net 3.5, у клиентов ХР". Никакого Ci/CD конечно, никакого докера, то есть в половине случаев разбираешься, что за мусор на конечной системе стоит и как оно все интерферирует с твоим приложением. Возиться с MSI, реестром… Брр, не, спасибо.
От комьюнити есть кроссплатформенные GUI-фреймворки Avalonia, Eto, GtkSharp, SpaceVIL.
Со стороны Microsoftв следующем году выйдет .NET Multi-platform App UI, что является эволюцией Xamarin.Forms. Multi-platform App UI (MAUI) будут кроссплатформенные для Android, iOS, macOS, Windows. Для этих платформ поддержку будет осуществлять сам Майкрософт. Для Линукса поддержка будет от комьюнити. Начиная с .NET 5, будут объединяться .NET Core и Mono /Xamarin в одну библиотеку базовых классов (BCL) и набор инструментов (SDK). Плюс еще другие улучшение по сравнению с Xamarin.Forms, так что код должен быть максимум унифицирован. В репозитории MAUI можно почитать подробнее и увидеть разницу между текущими Xamarin.Forms и будущим MAUI.
Также для любителей всяких Электронов, встречал проекты, которые делают что-то подобное на Блейзоре. Пока что это очень сырое и не подходит для нормально использования, но возможно через 1-2 года (с приходом AOT-компилятора) это будет юзабельное на уровне того же Электрона.
а WinForms сильно привязано к Windows API.
Тем не менее, приложения с WinForms вполне себе нормально работают под Linux в Mono. Просто Microsoft это не нужно. А вот WPF действительно не портировать, можно только запускать под wine.
Но в итоге моё приложение постоянно падало с MissingMethodException, так как много контролов и методов WinForms не было имплементировано. В общем, мне тогда этот порт WinForms показался достаточно ограниченным и со всякими приколами.
Возможно сейчас это лучше работает, но я не проверял и не слышал, чтобы кто-то это использовал.
WPF опирается на DirectX
Никогда не понимал, почему этот аргумент — аргумент. Ну и пусть оно под виндой работает над DirectX. Никто не мешает на других системах сделать его на базе OpenGL или вулкана. Было бы желание.
Никто не мешает на других системах сделать его на базе OpenGL или вулкана.
Wrong. Я посмотрю как вы подобный код прибитый гвоздями к директиксу будете переносить на вулкан...
Было бы желание
А его тоже нет. Большинство софта который сейчас пишется — это серверный b2b/b2c.
Вот здесь люди уже два года обсуждают кроссплатформенный WPF. Представители Microdsfot дали понять, что вкладывать в это ресурсы не собираются. А комьюнити так и не смогло особо определиться, какие прослойки/замены использовать для D3D использовать.
Так что никакого прогресса в этом плане вроде нет. Но я могу конечно ошибаться, может кто-то и работает над такой задачей.
Я прям удивлён: завезли всё то, что я так давно хотел видеть в языке.
Больше не надо писать Dictionary<SomeLongKeyType, SomeComplexType<ParamName1, ParamName1>> dictionaryName = new Dictionary<SomeLongKeyType, SomeComplexType<ParamName1, ParamName1>>();
Больше не надо писать if (!(someVariable is null)) ...
Больше не надо самому писать рекорды, костыли вместо нормальных инициализаторов, явное приведение к типу в тернарных операторах.
Единственное, чего не хватает — ну сделайте уже типы-перечисления реализующими IEquatable, наконец. Надоело EqualityComparer<>.Default.Equals
использовать.
вместо if (!(someVariable is null)) можно (и нужно) писать if (someVariable is {})
особенно полезно для if (someVariable is {} notNullVariable)
, потому что он например nullable распаковывает. Лично у меня все время происходит идиосинкразия когда мне нужно писать nullable.Value, даже если я только что проверил на то что оно не нулл, а GetValueOrDefault() вызывать опасно — мало ли кто проверку на нулл уберёт. А такая запись решает все проблемы без минусов.
Я стараюсь писать понятный код. А для человека, не особо знакомого с pattern matching, выражение if (someVariable is {}) ...
— абсолютно нелогичная и противоестественная конструкция. А вот if (someVariable is not null) ...
вполне очевидна и понятна даже тем, кто на C#2.0 пишет.
Вопрос настолько базовой привычки, что я даж не знаю. Плюс такой вариант короче. Плюс позволяет выделять ненулл переменную в новвую переменную, как я выше написал. Ну да, надо запомнить что is {}
проверяет, что переменная не нулл, как по мне никаких сверхспособностей для этого не нужно. Ну то есть
Я стараюсь писать понятный код.
А код someVariable is {}
надо полагать непонятный?
А код someVariable is {} надо полагать непонятный?
Да, неочевидный. JavaScript-ом уже попахивает.
А стрелочные лямбды не попахивают? А то вдруг надо писать delegate {...}
и вот это всё. Чтобы пахло сишарпом.
и вы так и не ответили что будем делать с нуллейблами:
Guid? x = GetGuid();
if (x is not null) {
???
}
Нет, стрелочные лямбды не попахивают. Вроде очевидный синтаксис.
и вы так и не ответили что будем делать с нуллейблами:
Не знаю, я их вообще почти не использую. А если и использую, то явно со свойствами HasValue и Value.
И вообще, здесь положено страдать, потому что использование одного и того же синтаксиса для Nullable и для nullable reference types — вселенское зло.
Нет, стрелочные лямбды не попахивают. Вроде очевидный синтаксис.
Не очень последовательно. Выглядит как скорее "стрелки давно, они попривычнее будут. А вот is {}
поновее, поэтому как жс". Хотя в ЖС никакого is
нет в принципе.
Не знаю, я их вообще почти не использую. А если и использую, то явно со свойствами HasValue и Value.
Ну то есть получаете проблемы "забыл проверить на HasValue — получил эксепшн"? Зачем, если можно получить гарантии от компилятора?
И вообще, здесь положено страдать, потому что использование одного и того же синтаксиса для Nullable и для nullable reference types — вселенское зло.
Зачем страдать больше если можно страдать меньше? Ну да, синтаксис такой, сделали хреново. Придется или с этим жить, или не писать на сишарпе. Я в процессе накапливания внутреннго настроя на второй вариант, но пока что следую первому.
Я бы просто сделал так:
bool GetGuid(out Guid guid)
вместо
Guid? GetGuid()
Булево значение, которое легко можно проигнорировать и для которого нужно помнить семантику vs типы, проверяемые компилятором? Какой-то ну очень неравноценный обмен.
Во-первых как выше сказали, значение может быть проигнорированно.
Во-вторых вы теряете все возможности использовать результат как экспрешн:
var guidHashString = GetGuid()?.GetHashCode?.ToString() ?? "No hash";
Нет, с типом Guid?
всё в порядке, информация о том что значение может отсутствовать должна быть в типе, а не в соглашении "ну если там бул и аут параметр ну значит быть он означает, было ли проинициализированно значение". Компилятор >> соглашения.
Не знаю, я их вообще почти не использую. А если и использую, то явно со свойствами HasValue и Value.
То есть нивелируя всю пользу, которые они могут принести?
И вообще, здесь положено страдать, потому что использование одного и того же синтаксиса для Nullable и для nullable reference types — вселенское зло.
А это ещё почему?
if (x is not null but y)
Шутка.
if (!(someVariable is null)) можно (и нужно) писать if (someVariable is {})
В данном случае вообще можно написать if (someVariable != null)
.
они по разному работают. Самый простой пример
void Main() { Console.WriteLine(IsNullA(new Weird())); Console.WriteLine(IsNullB(new Weird())); } static bool IsNullA(Weird obj) => obj == null; static bool IsNullB(Weird obj) => obj is null; class Weird : IEquatable<Weird> { public bool Equals(Weird other) => true; public override bool Equals(object obj) => true; public override int GetHashCode() => 0; public static bool operator ==(Weird left, Weird right) => left.Equals(right); public static bool operator !=(Weird left, Weird right) => !left.Equals(right); }
вы все ещё не можете надежно написать код с нуллейблами:
if (someVariable != null) { DoSomething(someVariable.Value); }
Вот этот
Value
тут не нужен, но без него достучаться до переменной не выйдет. При этом если иф по какой-то причине убрать в результате рефакторинга (а такое на самом деле часто бывает), то вместо ошибки компиляции будете потом эксепшны ловить. Ну а статически типизированные япы для того и нужны, чтобы компилятор проверял код, а не логгер эксепшнов на проде.
При этом
if (someVariable is {} some) { DoSomething(some); }
Таким проблемам не подвержен. Если убрать проверку, компилятор пожалуется что переменной some не существует, и лишнего вызова Value тоже нет.
Если же вас так смущают скобочки, ну пишите имя типа, это эквивалентно:
if (someVariable is Guid some) { DoSomething(some); }
Просто я не люблю писать лишнее, особенно для длинных типов вроде IReadOnlyDictionary<Something, SomethingElse>
. Никаких проблем запомнить что {}
означает не нулл я не вижу. Тем более, что в паттерн матчинге в свитч экспрешне все равно пригождается иногда, то есть запомнить все равно нужно.
они по разному работают. Самый простой пример
В этом случае да, но такая перегрузка операторов — редкость. Кстати, а компилятор оптимизирует someVariable != null
в простых случаях?
Такая перегрузка — редкость, но вообще бывает. Это ведь просто пример, для демонстрации.
Кстати, а компилятор оптимизирует someVariable != null в простых случаях?
Не совсем понял, что имеется в виду. Вызов != будет вызывать оператор, а вызов на is null/ is {} будет одной инструкцией. Для примера выше (рекламирую LinqPad):
IsNullA:
IL_0000: ldarg.0
IL_0001: ldnull
IL_0002: call UserQuery+Weird.op_Equality
IL_0007: ret
IsNullB:
IL_0000: ldarg.0
IL_0001: ldnull
IL_0002: ceq
IL_0004: ret
Если речь про ассемблер то так
C.IsNullA(Weird)
L0000: mov eax, [rcx]
L0002: mov eax, 1
L0007: ret
C.IsNullB(Weird)
L0000: test rcx, rcx
L0003: sete al
L0006: movzx eax, al
L0009: ret
Не совсем понял, что имеется в виду.
Имеется в виду: можно ли безболезненно заменять !(someVariable is null)
на someVariable != null
, если не используется перегрузка операторов?
Как оказалось — да. Компилятор генерирует абсолютно идентичный asm.
Можно, только принцип открытости/закрытости никто не отменял. Сегодня у класса не переопределен оператор, а завтра уже да. И код по-другому начинает работать. Это надо учитывать.
А так, основная аргументация вторая. Чтобы неправильное разыменования нулла давало ошибку компиляции, а не наллреф в рантайме при вызове .Value
Можно безопасно заменить на !object.ReferenceEquals(someVariable, null)
Нет, компилятор просто подставит вызов оператора !=
.
И в любом случае, вам придётся сравнить объект по ссылке в реализации Equals
:
public bool Equals(Weird other) => other is not null && ... ;
public bool Equals(Weird other) => other is {} && ... ;
public bool Equals(Weird other) => !ReferenceEquals(other, null) && ... ;
В данном случае вообще можно написать if (someVariable != null).
И это будет вызов оператора !=
, а не проверка значения на null
.
Только не if (!(someVariable is null))
, а ` if (!(someVariable is {} notNullVariable))
А разница в том, что при попытке использовать notNullVariable
в ветке else
в первом случае будет ошибка компиляции "переменная не инцииализированна", а во втором все соберется и будет падать в рантайме.
Так обсуждалось же веткой выше.
Можно писать
if (xxx is object)
Кто напишет верные результаты, получает рогалик
public Car SomeMethod(DateTime p)
{
return new() { Passengers = 2 };
}
public Car SomeMethod(DateTime p)
{
return new { Passengers = 2 };
}
public T SomeMethod(DateTime p)
{
return new() { Passengers = 2 };
}
public T SomeMethod(DateTime p)
{
return new { Passengers = 2 };
}
В третьем случае будет работать если явно ограничить T
:
public T SomeMethod() where T : Car, new() => new() {Passengers = 2};
Альтернативно, валиден код вида
public T SomeMethod() where T : new() => new();
Спасибо за статью, громадное количество текста)
Теперь что касается по фичам: увы, сишарп продолжает идти по принципу "накидаем сахара, а там видно будет". 99% новых фичей дешугарится в старые версии, в итоге просто чутка облегчают написание кода. При этом кардинально новых вещей, чтобы не копипастить по 100 раз одно и то же в язык не добавляют.
По пунктам
- Новые нативные и просто новые типы — ок, пусть будут
- Атрибуты у локальных функций — ок, пусть будут
- GetEnumerator как метод расширения — шаг в правильном направлении, но как всегда видин "сахарозный" подход. форыч вызвать на таких можно, а вот написать какой-нибудь
where T : IStaticEnumerable
не выйдет. Решилось бы тайпклассами - Discard pattern — вот это очень хорошо, давно пора. Надеюсь, они везде работают, а не как обычно, в частности в ду нотац… простите, linq-синтаксисе.
- Инструкции верхнего уровня в C# — идея ок, реализация конеш такая. Сколько они костылей воткнули ради этих глобальных Args и остального — с ума сойти. Могли бы просто поти по принципу раста/linqpad — потребовать просто
void Main()
написать, от 8 буковок разработчики бы не обломались, зато вся магия исчезла бы. if (vehicle is not Car car)
— если за пределами if можно будет воспользоваться переменнойcar
то фича годная, иначе — не заменяет того варианта сif (!(vehicle is Car car))
if (context is {Length: > 0 and (< 10 or 25) })
— а вот это уже пушка. Мне просто жаль ребят из команды парсера и компилятора, которые впиливали вот этот новый dsl. Плюс вот лично мне тут ваще непонятно, в какой момент мы работаем с контекстом, а в какой момент начинаем считать его элементы. То что в языке появляются and и or которые как && и ||, но не совсем. В итоге эта фича для меня идет просто со знаком минус.- Улучшенный pattern matching в swtich expression — как выше уже вроде писали, это уже в текущей версии присутствует. В целом, полезная штука. Давно пора было вместо бесполезного свитча сделать паттерн матчер.
- Новый new, а также в принципе улучшенный target typing — опять же, фича с триллионом ограничений, и в реальности почти всегда ненужная потому что используется var. Поможет только в инициализаторах полей и тестах без AutoData. В целом ничег вредного, но и ничего особо полезного
- Target typed операторы ?? и ?: — ну, тут ничего не скажешь, наконец-то научили их не тупить в очевидных сценариях. Фича со знаком плюс, хотя и сахар, и маленькая
- Переопределение возвращаемого типа методов — тоже полезная штука, ценна в частности для реализация какого-нибудь
Clone()
который должен возвращатьThis
— то есть для абстрактной фигуры — фигуру, но для треугольника — треугольник. Одна из двух несахарных фичей, однозначно плюс - init-свойства — это не совсем readonly-члены — отлично пункт назван. У нас уже есть ридонли поля, ридонли свойства, свойства с приватным сеттером, теперь добавляются еще инит-свойства. Зачем они нужны для меня до сих пор загадка, особенно если учитывать что добавили нормальные рекорды. Предлагаю шараду: угадайте, какой из вариантов инициализации что означает:
public class Person(string FirstName, string LastName) public class Person { string FirstName; string LastName; } public data class Person { string FirstName; string LastName; } public class Person { string FirstName {get;set;} string LastName {get;set;} } public class Person { string FirstName {get;init;} string LastName {get;init;} } public class Person(string firstName, string LastName)
- Record — это узаконенная DTO'шка — ну, это хорошая штука. Хотя с полноценными генераторами делать целую языковую фичу под это было бы необязательно. В целом, плюсовая фича. Но опять сахарная
.NET Source Generators — а вот это вторая несахарная фича, но как всегда, майкрософт пошел на кучу компромиссов. Один из важных кейсв — сурс генераторы не могут быть вложенными. то есть сгенерировать код из которого будет сгенерирован код нельзя. В частности, в это точно упираются ребята из asp.net которые планировали рейзор перевести на сурс генераторы (а пока останутся на рефлекшне как и раньше), ну и в целом, это кажется блажью только пока не попишешь немного макросов, а там сразу становится понятно, что без этого фича так же ограничена, как ООП с наследованием только одного интерфейса. Во-вторых сурс генераторы не могут менять сигнатуры. Что это означает? Ну, что например атрибут "добавить неявный CancellationToken и прокинуть во все вложенные методы" (этакий АОП) сделать не выйдет. Но что важнее, не выйдет сделать юзерские рекорды. Возьмем раст, там рекорд это обычная структурка на которую навешана пара сорсгенераторов:
#[derive(Clone, Eq, PartialEq, Hash)] struct Person { first_name: String, last_name: String }
которое раскрывается примерно в
Person : Clone, Eq, PartialEq, Hash { ... }
. То есть меняется сигнатура (добавляются реализации интерфейсов). В шарповых сорсгенераторах такого не будет.
- Частичные методы (partial method) — сахар, особо не нужен (кроме как в контексте предыдущей фичи)
- Методы-инициализаторы (module initializers) — вредная магия, но без которой генераторы видимо совсем плохие.
Ну и какой итог отсюда можно сделать? Добавили ли в язык полезных фичей? Ну, добавили. Стал ли он удобнее в использовании и т.п.? — Да, стал. Стал ли он удобнее пропорционально добавленной сложности? — однозначнно нет. Сахар, который плох именно тем, что не добавляет ортогональности и не позволяет комбинировать его, фичи с кучей оговорок, и прочее — язык очень сильно усложняется, а вот полезного к сожалению не очень. Нужные вещи вроде полноценных макросов, шейпов, типов высших порядков — откладываются из релиза в релиз. Простейшие рекорды которые наверное единственное стоящее нововведение новой версии языка обещали с 6(!) версии больше пяти лет.
Всегда очень нравился сишарп. Но чем дальше, тем больше понимаю, что для продуктивной разработки он мне все меньше и меньше подходит. И не потому что он становится хуже, нет, конечно же нет. Но как говорила Алиса, тут нужно бежать, чтобы оставаться на месте, а чтобы куда-то попасть нужно бежать ещё сильнее. Другие языки бегута куда лучше, даже Java на которую мне всегда было больно смотреть без слез с каждым релизом выглядит все более и более достойно. Увы, на мой взгляд сишарп бежит едва достаточно чтобы просто оставаться на этом самом месте. А жаль.
.NET Source Generators — а вот это вторая несахарная фича, но как всегда, майкрософт пошел на кучу компромиссов.
Огромное разочарование, согласен. Я так подозреваю, разрабы IDE отказались делать поддержку чего-то нормального. В результате этот кривой бесполезный костыль.
Другие языки бегута куда лучше, даже Java на которую мне всегда было больно смотреть без слез с каждым релизом выглядит все более и более достойно.
Я не очень активно слежу за Java, но у них проблем ещё больше, по-моему. Перетаскивания обещаний много версий подряд вовсе не меньше. Сколько там лет Project Valhalla? Царского наследия, портящего современный дизайн, больше.
Джавовый мир движется вперёд за счёт игнорирования джавы, и у меня пока не складывается ощущение, что котлин ощутимо лучше шарпа хоть в каком-то измерении, будь то мощь или популярность. Пока что я его воспринимаю как Swift — возможность писать на нормальном языке, когда ограничен платформой.
Ну да, шарп не движется так быстро, как хотелось бы. Но где альтернатива-то?
у меня пока не складывается ощущение, что котлин ощутимо лучше шарпа хоть в каком-то измерении, будь то мощь или популярность
DSL. На котлине приятнее выглядит любой DSL, потому что есть лямбды с имплисит-ресивером и немножко другого мелкого сахара. Посмотрите на адище под названием Cake на C# и на любой DSL на котлине.
У нас есть один микросервис, в котором уже ушедший из компании разработчик решил поюзать резалты. Ну, шобы надежно и все такое. В итоге, весь код обернут во все эти OnSuccess(() => ...)/OnError(() => ...)
. И мне вчера человек жаловался, что у него и студия, и райдер тупо лагают от такого количества лямбд :) Так что вопрос не только в эстетике.
А проект мелкий, пара десятков тысяч sloc.
Сейчас можно делать switch с деконструкцией, но без нормальной поддержки DU в C# он всё равно не до конца удобен:
- нужно всегда выписывать полный тип: не
OK(var value)
, аResult.OK<Foo, Bar>(var value)
- компилятор не догадывается, что других вариантов кроме OK и Err быть не может, и жалуется, что не проверен
_
Ну свитч с деконструкцией все же не всегда выручает, например Есть у нас некий Either<int, Exception>
, и задача сделать "если не ошибка то умножь значение на два иначе ничего не делай". Со свитчем будет очень длинно и неудобно. Про отсутствие exhaustive и общее количества бойлера говорить не приходится, вы и сами про это написали.
Ну да, шарп не движется так быстро, как хотелось бы. Но где альтернатива-то?
Джава вполне себе альтернатива. Если работать в ЖБ то есть котлин, смысл которого правда становитя все меньше с тех пор, как комитет по джаве перестал топтаться на месте из версии в версию. Алтернативы: самая близкая мне по духу — скала, есть все нужные фичи, распространенность высокая, ЗП одни из лучших на рынке, но синтаксис такой себе, имплиситы вместо тайпклассов тоже на любителя. Хаскель — язык почти во всем лучше всех альтернатив, но распространен мало, поэтому если на него делать ставку, то придется ограничиться не таким большим числом вакансий, и искать/менятть работу может быть проблемой. Ну и если байтики нравится перекладывать, то раст отличная альтернатива. Почти все нужные фичи есть, писать удобно, из минусов опять же меньшая популярность чем у шарпа.
Так что альтернативы есть, но лично у меня ловушка в том, что просаживаться по доходам на переобучение другому япу не хочется пока, а пойти сразу на синьор помидора на незнакомый стек не позволяет наглость. Думаю, в следующем году буду разрывать порочный круг.
P.S. знакомый из шарп чатика как раз месяц назад на хаскеле работу нашел, сидит пилит. Вот он — большой молодец)
А какая версия джавы у вас?
Ну так жабу 10 надо сравнивать с C# 4.0 :) Это как раз версия конца эпохи застоя. На 14 имхо сильно лучше стало
+1. Как скальщик (кстати, в скале большая часть вышеперечисленного уже есть, и очень радует, что добавляют в шарп) могу сказать, что тоже есть препятствия для перехода на новые версии JVM (например, Spark и Flink только недавно научились работать с 11 версией (когда вышла 14)).
В C# версия языка и версия фреймворка — вещи, не связанные тесно.
Вы можете спокойно писать на C# 9.0 под целевую платформу .NET Framework 4.0, и даже под 3.5, и даже с async-await из-за совместимости IL-кода. Главное, чтобы библиотеки нужные были.
Ну не так совсем. Асинк-авейт под 2.0 не заведется (без очень грязных хаков), дефолтные методы интерфейсов тоже, и так далее. Если покопаться, то там не все так просто. Но "сахарные" фичи, коих большинство — да, заведутся
Писать можно, но поддержки от MS таких комбинаций, начиная с С# 8.0, не будет
Поддержка есть, просто не все возможности C# 8.0 будут работать, например, default interface methods.
https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/configure-language-version
C# 8.0 is supported only on .NET Core 3.x and newer versions.
C# 9.0 is supported only on .NET 5 and newer versions.
Понятно, что никто не запрещает руками поменять версию языка в csproj, но Microsoft не гарантирует, что это будет работать вообще или будет работать корректно. "Choosing a language version newer than the default can cause hard to diagnose compile-time and runtime errors."
На метод накладываются некоторые ограничения:
он не должен работать с обобщениями (generics);
Либо ошибка перевода, и на самом деле там просто запрещено делать такой метод обобщенным, либо такие методы настолько убогие и в них нельзя даже простой словарь создать и заполнить.
Вероятно, метод просто не должен принимать ни аргументов, ни generic-аргументов.
[ModuleInitializer] internal static void Init() { /*...*/ }
[ModuleInitializer] internal static void NotInit<T>() { /*...*/ }
Ошибка перевода.
https://docs.microsoft.com/ru-ru/dotnet/csharp/language-reference/proposals/csharp-9.0/module-initializers
The method must not be generic or be contained in a generic type.
Статья супер, спасибо! Кажется, самая подробная из всех, что я видел — на английском в том числе.
спасибо! очень люблю C# а теперь люблю ещё больше <3
Монада — это не фича, а просто интерфейс определенного вида. Точно так же как IEnumerable — не фича. Так что вопрос неясен.
Discriminated unions — это один из двух компонент для ADT, вместе с тип-произведениями (то есть просто классами). И да, их очень не хватает. Эмуляция на объектах дорогостоящая и неудобная. Не сказал бы что это позволяет определять одинаковые операции для разных типов, это скорее энумы на стероидах, в которых кроме самого значения могут быть дополнительные данные:
enum Command {
Login { string UserName, string Password },
ChangePassword { string NewPassword },
Logout
}
Данные-то у нас тут везде примерно одинаковые, строки, но пользы полно
https://weblogs.asp.net/dixin/category-theory-via-csharp-7-monad-and-linq-to-monads
As Brian Beckman said in this Channel 9 video:
LINQ is monad. It is very carefully designed by Erik Meijer so that it is monad.
Eric Lippert also mentioned:
The LINQ syntax is designed specifically to make operations on the sequence monad feel natural, but in fact the implementation is more general; what C# calls "SelectMany" is a slightly modified form of the "Bind" operation on an arbitrary monad.
Или реализация разрешена только 1 раз и в остальных случаях нужна ";" после объявления метода?
Как я писал выше сейчас у нас в работе под C#10 из интересностей «деконстукторы ограничений обобщенных типов». И обсуждаем наследование enum
я уверен, что еще в примерно во времена visual studio 2013 эта фича уже была в C#
Раньше метод был частичным, если он не возвращает значения и не имеет out-параметров для того, чтобы вызов этого метода ни на что не влиял, если у него нет реализации и, соответственно, вызова. Поэтому он не мог быть частью публичного интерфейса.
Судя по описанному выше (я не проверял), публичный частичный метод или частичный метод, возвращающий значение, обязан быть реализован где-то в другой части класса.
Простой пример, даже не из новых, а откуда-то из глубин C#4
var contact = new ActiveContact();
вроде всё ясно читается с листа. Но потом Вася переписал код на
var contact = schema.GetContacts("output");
какой теперь тип у contact? Правильно, не знаем, надо дополнительно лезть и смотреть. А если таких в теле метода десяток, то лезть и потом в голове удерживать.
Предвижу вечнозелёное: «Используйте тулзы!» Можно и «тулзы», но можно просто не полениться и набить «лишний» код, который, как показывает практика, с течением времени перестает быть лишним. Ведь программирование, хотелось бы надеяться, не просто набивание на клавиатуре, а в основном еще и некая мыслительная деятельность.
какой теперь тип у contact?
Это важно во вторую очередь. В первую важно что делает GetContacts и почему справа множественное число, а слева — единственное.
Тип я посмотрю так же как и документацию либо реализацю GetContacts. Занудное уточнение типов не засоряет общий план алгоритма.
Про новинки в .NET 5 и C# 9.0