Комментарии 105
Сильно раньше, кмк, вот статейка от мая 2018-го https://docs.microsoft.com/en-us/aspnet/core/tutorials/dotnet-watch
Просто выглядит так, что теперь этот режим поддерживается не только из консоли, но и в IDE
Что будет дальше? Добавление возможности писать прямые ассемблерные инструкции в коде?
Шутка? Давно писали жеж.
Ну да, есть интристики.. по сути уже можно начиная с Core 3.0.
Я недавно сравнивал производительность C# и C++ в вычислительных задачах на примерах базовых алгоритмов обработки изображений с интринсиками. Получил результат, что C++ быстрее C# где-то процентов на 20. Так что да, на C# уже смело можно писать высокопроизводительный код.
Так а писать высокопроизводительный код на шарпе никогда особо не было проблемой. Если стоит задача на десктопе, например, обработать картинку и показать юзеру, то случится это за 0.5 с или 0.6 с - не важно. На сервере для обработки картинок пачками - тоже не важно, ведь всегда можно добавить ядер, ОЗУ или больше инстансов и обрабатывать столько картинок в секунду, сколько нужно.
Но всё становится совершенно иначе, когда картинку нам нужно обрабатывать для автопилота, для отображения на спидометре или для экране медицинского прибора. Тут важно не "вцелом почти так же быстро", а точное значение потребляемой памяти, точные моменты выделения и освобождения памяти, точное количество инструкций, тактов и милисекунд на обработку. И тут по-прежнему как не было места управляемому коду, так и не будет.
Но всё становится совершенно иначе, когда картинку нам нужно обрабатывать для автопилота, для отображения на спидометре или для экране медицинского прибора. Тут важно не "вцелом почти так же быстро", а точное значение потребляемой памяти, точные моменты выделения и освобождения памяти, точное количество инструкций, тактов и милисекунд на обработку. И тут по-прежнему как не было места управляемому коду, так и не будет.
Вот только тот же Сименс для своих CT/MRT вполне себе пишет код на C#. В том числе и для обработки изображений. To есть про это я точно знаю. И наверняка для каких-то других медицинских приборов это тоже так.
Был как-то на докладе про Java для "High-frequency trading". Только я бы не смог назвать это Java. Garbage collector не использовался, память выделялась и освобождалась вручную, почти все функции статические, а классы final. В общем-то у многих после доклада был вопрос, а зачем Java если из специфичных для нее возможностей ничего не использовалось.
И не совсем понятно с МРТ, одно дело управлять и собирать данные во время работы, другой вопрос обработка уже собранных данных постфактум для отображения на экране монитора. Для второго никакой работы в "реальном времени" не нужно и можно эту часть реализовать на чем угодно. А вот первое писать на языке с "garbage collection" довольно странно.
Пруф что шарп на приборе исполняется будет или сразу начнёте шарманку что имели в виду не прибор, а бэкенд сервера и десктоп приложения на компьютере доктора?
Ну вот банально у самого Сименса находится открытая вакансия: SPECT & PET Acquisition system for next generation MI scanners(C#, WPF).
А вот ещё одна(C#/.Net Core on Linux) под Point of Care Diagnostics.
Это на приборе исполняется?
Ну обычный ассемблер — это слишком, а вот вставки IL кода были бы очень кстати для высокопроизводительного или специфичного кода (который не описать на C#). Есть дискуссия на гитхабе: Proposal: Inline Languages.
Отвечая на коменты к моему посту про асм...
Да коммон ребята, были вставки на асме в шарпе, возможно они закончились на х86 перед переходом на х64, или какие-то ограничения наложились, дальше просто не следил. Но они были и хорошо работали. Основная проблема с ними была что специфичные команды, для которых асм вроде и нужен, мсовский компилятор не понимал, так что все равно брался в руки С++, интеловские компайлер и подключался как всегда через нативную dll. А на простых командах встроенный асм норм работал.
Да и залезте вы внутрь fw2, там внутрях куча примеров использования асма.
Но людей с определённым профессиональным опытом настораживает попытка создать универсальную платформу для всего сразу.
Ну, люди с "определённым профессиональным опытом" должны понимать (я уверен что понимают), что ситуации бывают разные и иногда нужно выжимать из кода всё, что возможно. Пусть только в определённых точках кодовой базы. Убеждения, что для этого нужно писать код на Си, они хреновенько выглядят на практике ибо их трудно поддерживать, появляются гетерогенные (или как это назвать) проджекты с обертками для нативных вызовов. Тяжело билдить. Тяжело поставлять. Неприятно (иногда) дебажить.
Единственную серьезную проблему, которуя я здесь вижу, это офигевание человека который через пару лет начнёт учить .net. С другой стороны, никто не форсит использоваться сразу всё. Вполне себе можно погружаться постепенно и более опытные товарищи подскажут, как платформа позволяет это же сделать еще лучше.
Оо да... В добавок, уже на многие вопросы, которые были актуальны в 2010, перестали таковыми быть сейчас.
К примеру, вопросы об аллокации классов уже не совсем не однозначные ибо в core 3.0 сделали escape analysis и теперь при должных условиях класс улетает в стек. Или вот сегодня утром читал свежевышедшую статью (на хабре есть её укороченная версия) Performance improvents in .NET 6.0 . Насколько понимаю, что .NET 6.0 при определенных условиях сможет сапрессить баудс чек массивов. (Я её еще не дочила до конца ибо она огромная, но подозреваю, что сюрпризов там достаточно).
Насколько понимаю, что .NET 6.0 при определенных условиях сможет сапрессить баудс чек массивов. (Я её еще не дочила до конца ибо она огромная, но подозреваю, что сюрпризов там достаточно).
Вообще это уже давно есть, по крайней мере для простых случаев типа:
public void M(int[] array) {
for (int i = 0; i < array.Length; i++) {
Console.Write(array[i]);
}
}
Но наверное в статье идет речь о чем-то более продвинутом.
в core 3.0 сделали escape analysis и теперь при должных условиях класс улетает в стек
Не сделали в итоге. Что добавляет конфузий, да.
Я на прошлой неделе, кажется, еще писал тест на пятой коре.
В GC появился метод который возвращает аллоцированные байты. Выделяет класс. Смотрите разницу. Класс в стеке. выделено ноль. Коммит, вроде бы тоже как в мастере.
https://github.com/dotnet/coreclr/pull/20814
Они описывают как "limited support". Но всё же должен быть.
Тестил я на очень простом кейсе. Собственно, и сам тест я взял из юнит тестов к коммиту. Было интересно поиграться.
Эта репа давно неактуальна.
Нужная - dotnet/runtime
Покажи полный код, вполне возможно что просто вырезано джитом.
аа.. вот балда.. значит наврал.
Там по ссылке, в коммите есть юнит тесты.
Gc
Я был уверен, что сделали. Даже в .NET Blog упоминали, что добавили (limited). long allocatedBytesBefore = GC.GetAllocatedBytesForCurrentThread();
int testResult = test();
long allocatedBytesAfter = GC.GetAllocatedBytesForCurrentThread();
ну, и между ними засунуть, что-то. Да, только не забыть обратиться после, дабы реально не вырезал. У меня получалось ноль на примитивных классах.
Действительно делали, во внутренних тестах не нашли импакта и не стали мержить в мастер.
По итогу в неткоре нет полноценного ескейп анализа, но многие ад хок кейсы джит и так оптимизирует. Новый инлпйнер в нет6 топчик в этом плане.
Всё я нашел
В комментариях указанно, что выключено по-умочанию https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-core-3-0/.
Дальйшую судьбу узнал вот только от тебя.
Спасибо, за уточнение!! Дейсвительно запутали еще больше.
Инлайнер - да. Нужно будет вечером засеть и разобрать внимательно статью. Капец она здоровая.
Единственную серьезную проблему, которую я здесь вижу, это офигевание человека который через пару лет начнёт учить .net
Я учу .NET прямо сейчас, пытаясь из электронщика/системного программиста перестроиться в полосу C#-разработки. Не сказать, чтобы прям полыхает, но подгорает знатно.
Существенный объем информации плюс отсутствие поблизости более опытных товарищей делают продвижение к каким-то осязаемым вершинам довольно медленным.
Поздравляю, я шел по такому же пути в 2012-ом))) тогда учить C# было заметно проще))
А я вот обратно хочу, поближе к железу. И тупизна C и C++ меня вымораживает.
Это кстати ответ автору зачем. Когда нибудь можно будет ОС и драйверы на шарпе писать, но я наверное не доживу :)
Ps да я в курсе(смотрел) про singularity OS, cosmos итп...
Пожалуйста, не надо драйверов на шарпе. Только сборщика мусора в ядре и не хватало...
Что же до близости к железу — учтите, что даже со всеми нововведениями "нативная" часть С# ещё тупее языка Си. Написать таким образом кусок критического к производительности кода или прослойку для взаимодействия с библиотекой ещё можно, но если писать так всю программу — зачем вообще выбирать C#?
К слову о близких к железу языках, Rust смотрели?
Писать можно и без сборки мусора. Пулинг никто не отменял. Зачем? Если сравнивать си, то это безопаснее. В этом критичном к производительности куске после, находят парочку дыр.
Но, да, есть Rust. Я его плохо знаю, пока не в курсе насколько он справляется с типичными сишными проблемами.
За счёт чего низкоуровневые возможности C# безопаснее Си?
Ну, например, за счёт контроля выхода за границы области памяти при использовании низкоуровневого Span<T>
.
Ну да, от переполнения буфера спаны защищают, тут соглашусь.
Но посмотрите на NativeMemory, про который написано в обсуждаемом посте. Это что, безопасное API?
Или возьмём идею "пулить" всё подряд. Да, сборку мусора оно "починит" — вот только в обмен будут все те же проблемы Си с ручным управлением временем жизни.
Или возьмём идею "пулить" всё подряд. Да, сборку мусора оно "починит" — вот только в обмен будут все те же проблемы Си с ручным управлением временем жизни.
Не починит, а только усугубит, скорее всего. Непонятно откуда берется идея, что выделение большого числа объектов это плохо. На скорость сборки мусора влияет количество живых объектов, а не выделенных. Если для обработки запроса выделить 100 000 объектов и через 100ms все они уже не нужны, то все ок, GC их даже не увидит. То есть, внимание, стоимость создания и удаления 100 000 объектов в этом случае равна почти 0. А если сделать пул из 1 000 000 объектов и каждую секунду каждый из них каким-то запросом используется и изменяется, то GC будет вынужден все 1 000 000 объектов проверить и это будет долго.
Есть еще кейс, когда объектов выделяется так много, что тупо память заканчивается за те самые 100ms и тогда наступает п..ц и все встает колом.
Непонятно откуда берется идея, что выделение большого числа объектов это плохо.
Если бы это было не так, то разработчики .NET не парились бы с использованием всяких Span для строк и не только, которые вообще позволяют обойтись без созданий объектов в куче.
Выделять объекты дёшево или даже бесплатно, а вот копировать данные из нативного буфера в поля этих объектов нет. Тут пригождается Span.
При создании любого объекта задействуется куча, это не бесплатно, стек легче. Ну и даже пустой объект хранит в себе несколько байт.
Я его плохо знаю, пока не в курсе насколько он справляется с типичными сишными проблемами.
Замечательно, после перехода на Rust волосы станут мягкими и шелковистыми вы забудете про гонки данных, доступ к неинициализированным значениям, use after free и buffer overwrite. А, ну и, конечно, типобезопасные обобщённые функции, вывод типов и нормальная модульность без деления на заголовочные файлы и файлы реализации и без include guard.
Уже была написана, как тестовый проект.Да Singularity называется. Если верить Торвалдсу, то ООЯ не очень хорошо подходят для написания операционных систем.
(не совсем понимаю почему)
но может ему виднее. На том же Шарпе, вполне можно писать достаточно низкоуровнево.
Хз, может когда-то будет.
Ну, я начал учить его с 4.0 кажется. Но до этого уже немного знал Си. Это было сто процентов проще нежели сейчас. Могу только пожелать терпения. .net очень обширный.
А перед вами точно стоит задача "изучить весь C# от корки до корки"?
Так-то учить весь язык целиком и сразу для написания программ не обязательно...
Я бы сказал что даже если не стоит цель "от корки до корки", то порог вхождения как таковой сейчас однозначно выше чем он был 10-15-20 лет назад.
То есть условный "Hello World" сейчас так же просто написать как и раньше. Или в принципе что-то совсем простое. Но даже туториалы и "прикладные" примеры содержать больше информации чем раньше.
То есть на мой взгляд те же LINQ или async/await сейчас вполне уже можно ожидать от человека, который хочет зарабатывать деньги шарпом.
На самом деле, LINQ и async/await — это не уникальные возможности шарпа.
В JavaScript, например, принцип работы с асинхронным кодом абсолютно аналогичен.
Во первых речь не о том что это что-то уникальное. Речь о том что 10-15 лет назад этого в шарпе не было. То есть если вы тогда хотели зарабатывать деньги шарпом, то вам не нужно было это знать. И следовательно порог вхождения был ниже.
А во вторых насколько я знаю работа с асинхронным кодом шарпе и джаваскрипте только выглядит похоже. Принципы там как раз таки разные.
П.С. Ну и как бы я отношусь к тем "счастливчикам", которые познакомились с шарпом на самом старте. И "расти" вместе с ним было относительно комфортно. Особенно где-то версии с третьей. Но я вижу какие проблемы возникают у людей, которые сейчас приходят в шарп и на которых зараз вываливают всё что с тех пор накопилось в языке.
Ну а я пришёл в C# когда там LINQ уже был. Но это мне ничуть не мешало первые месяцы его игнорировать и писать как будто его нету, а потом на ходу его изучить.
Вы пришли когда LINQ уже был или когда LINQ уже этаблировался? То есть я например тоже не начал его использовать сразу когда он появился.
Но в какой-то момент он стал использоваться плюс-минус везде. И если вы хотя бы не понимали как он работает, то банально не понимали как работает куча чужого кода. А сейчас его уже даже и на собеседованиях спрашивают или просто ожидают что вы его используете в тестовых заданиях.
А во вторых насколько я знаю работа с асинхронным кодом шарпе и джаваскрипте только выглядит похоже. Принципы там как раз таки разные.
Ну-ка давайте поподробнее, в чём же заключается принципальное отличие, если в обоих случаях это просто синтаксический сахар для колбэков.
"Синтаксический сахар для колбэков" это была "предыдущая версия" асинхронности в шарпе. Она же Event-based Asynchronous Pattern (EAP) . А сейчас у нас Task-based asynchronous pattern(TAP)
Плюс если я не ошибаюсь асинхронность в джаваскрипте у нас всё ещё однопоточная. В отличие от шарпа. Что добавляет приколов вроде контекста выполнения. Хотя тут я уже не уверен. Потому что не особо слежу за развитием джаваскрипта.
Ну нет, EAP это другое, "колбеками" неформально называют CPS, который в некотором смысле эквивалентен TAP.
Рассмотрим типичный асинхронный метод в стиле передачи продолжений. Его сигнатура будет примерно такой:
(TParam1, …, (TResult → void)) → void
Немного каррируем этот метод:
(TParam1, …) → (TResult → void) → void
Теперь упакуем всё, что после первой стрелки, в новый тип данных — Future/Promise/Task, что больше нравится:
(TParam1, …) → Task TResult
Оп! У нас получился TAP.
Что же до многопоточности, контекстов выполнения и прочего — это всего лишь детали реализации.
Ну нет, EAP это другое, "колбеками" неформально называют CPS, который в некотором смысле эквивалентен TAP.
Ок, у меня может быть проблема с терминологией. Но TAP позволяет разделить момент создания таска и момент его запуска/выполнения(блин как же мне уже сложно писать технические вещи на русском...). EAP этого не позволяло. Насколько я помню в джаваскрипте это тоже нельзя разделить. Для меня в своё время это было одним из основных изменений.
Что же до многопоточности, контекстов выполнения и прочего — это всего лишь детали реализации.
Ну не знаю. Асинхронность в шарпе "намертво привязана" к многопоточности. То есть естественно это всё дело можно выполнять и в одном потоке. Но создавалось оно на мой взгляд совсем не для этого.
TAP позволяет разделить момент создания таска и момент его запуска/выполнения
Что требуется примерно никогда. Кроме того, async-методы в том же C# никогда не возвращают подобных задач, так что особенностью именно в async/await-подхода в C# это точно не является.
EAP этого не позволяло.
Ну да, за отсутствием самого понятия задачи. А если вынести понятие задачи за скобки — то как раз EAP только в таком режиме и работает:
foo.BarComplete += …; // создание "задачи"
foo.BarAsync(); // запуск "задачи"
Асинхронность в шарпе "намертво привязана" к многопоточности.
Не понимаю в каком смысле она "намертво привязана".
Что требуется примерно никогда.
Что требуется время от времени. Я могу насоздавать тасков и передать их кому-то. Он вообще не будет знать что это за таски но при этом выполнять их или нет в зависисмости от своей логики.
Не понимаю в каком смысле она "намертво привязана".
Например тем что это всё дело базируется на managed threading и без него нежизнеспособно?
Э-э-э, а почему без managed threading оно нежизнеспособно?
В смысле "почему"? Потому что оно его использует. Ну то есть в теории можно написать какой-то новый async/await, который будет базироваться на чём-то другом и обойдётся без managed threading. Но нынешний вариант сделан именно так.
Хорошо, что вы понимаете под managed threading и чем оно отличается от простого threading?
И второй вопрос - в каком виде оно используется реализацией async/await?
mayorovp, DistortNeo прежде чем я начну тут расписывать моё понимание работы async/await, может вы мне просто объясните в чём их смысл в абсолютно однопоточном мире? И в чём преимущество перед синхронным исполнением? А то может это я чего-то не понимаю.
Ответ будет из двух частей.
Смысл непосредственно async/await — в упрощении асинхронности. Эта конструкция позволяет делать асинхронные вызовы последовательно.
Смысл же асинхронности — в возможности делать ввод-вывод параллельно.
В сумме и получается преимущество async/await перед синхронным исполнением — оно позволяет произвольным образом комбинировать последовательные и параллельные участки кода, чего синхронное исполнение в одном потоке делать не может.
Я может опять чего-то не понимаю, но что такое параллельное выполнение в абсолютно однопоточном мире?
Например, вы пишете код, который не делает вычислений, зато много работает с сокетами и таймерами.
Вы можете раскидать работу с сокетами по разным потокам, делать синхронные вызовы, но получите дикий оверхед на потоки, на переключение между ними, на межпотоковую синхронизацию.
Операционные системы предоставляют механизмы асинхронной работы с дескрипторами. Вы делаете запрос ОС на выполнение какой-нибудь операции в асинхронном режиме, эта операция выполняется в фоне (как это реализовано внутри ОС, нас пока не интересует) и затем вы дёргаете результат выполнения операции, когда ОС сообщает о завершении операции.
Поэтому вы вспоминаете сначала про select, затем про epoll, IOCP и переписываете код в event-based стиле. И получается очень эффективно. Например, nginx — однопоточный*. Он может держать тысячи соединений, и вся обработка идёт в одном потоке.
Но писать код в event-based стиле неудобно, очень неудобно. И тут нам приходят в помощь async-await. Получаемый код выглядит как синхронный и многопоточный, но под капотом это эффективный event-based код.
*несколько потоков тоже может задействоваться, но обработка одного и того же соединения осуществляется только в одном из них.
Я может опять чего-то не понимаю, но что такое параллельное выполнение в абсолютно однопоточном мире?
Ну вот смотрите. Отправил я в сокет #1 первый запрос, потом отправил в сокет #2 второй запрос, теперь жду ответов. Где-то там сервера эти запросы параллельно обрабатывают, и им плевать сколько у меня потоков.
Ок, это я уже стормозил...
Вот интересно. Если стормозил и написал полную чушь с технической точки зрения, то получаешь пару заслуженных минусов. Заслуженных. Но всего пару.
А если написал неприятное но как минимум легитимное мнение по не техническому вопросу, то минусов прилететь может на порядок больше. Неладно что-то в датском королевстве...
Например тем что это всё дело базируется на managed threading и без него нежизнеспособно?
А вы вообще в реализацию планировщика залезали? Лично я в своё время писал собственный планировщик: Net Core ещё ходил под стол, а поддержка асинхронщины в Mono под Linux была сделана через жопу, поэтому пришлось изобретать велосипед.
Короче, нет там никакой привязки к потокам. Единственная привязка — это thread static переменная SynchronizationContext.Current с методами типа Send/Post и аргументом типа Action. А где этот делегат будет вызываться, решает сам планировщик.
Ну не знаю. Асинхронность в шарпе "намертво привязана" к многопоточности. То есть естественно это всё дело можно выполнять и в одном потоке. Но создавалось оно на мой взгляд совсем не для этого.
Нет, не привязана. Например, если вы в обработчике UI-события захотите сделать асинхронный вызов, то все задачи будут выполняться в Event Loop в единственном потоке. Так что всё зависит от того, какой SynchronizationContext используется.
Плюс если я не ошибаюсь асинхронность в джаваскрипте у нас всё ещё однопоточная. В отличие от шарпа
Всё зависит от планировщика. В своих проектах C# я, например, использую однопоточный планировщик.
Вот LINQ как раз уникальная возможность. Ну, в теории это вроде то самое квазицитирование, которое со времён LISP известно — но вот такого чтобы оно активно использовалось в экосистеме мне за пределами экосистемы C# найти не удалось.
С каждым новом релизом .NET'а порог вхождения улетает всё выше и выше в небеса. Таким темпом плюсы догонят.
+)
Как видно из примера, в случае равенства приоритетов порядок извлечения элементов не гарантирован.А вот это противоречит самой идее очереди как структуры данных. Назвали бы её тогда уж как PrioritySet или типа того.
"Приоритетная очередь" или "очередь с приоритетами" — это устоявшееся название, поздно его менять уже.
Можно, но надо добавлять последовательный счётчик как часть приоритета, и в большинстве случаев это будет замедление на пустом месте. А если строгий порядок и правда нужен — подобное легко добавить и самому, благо есть кортежи с лексикографическим порядком.
У меня вопрос к знающим людям про Xamarin Forms / MAUI. Можно ли уже сейчас сделать нативное приложение из одной кодовой базы под все платформы? В первую очередь интересует macOS и iOS, во вторую Windows и Android.
В смысле, чтобы можно было как во Flutter (я не фронтендер и не мобильщик, просто хочется иногда для части своих датасайнс задач сделать не вебную, а нативную морду), сейчас Flutter немножко тыкаю, но вот интересно, есть ли еще какая альтернатива? (Compose от JetBrains еще в зачаточном состоянии, как я понял).
Я бы сказал что если вы хотите использовать .NET, то на данный момент лучший кандидат для кроссплатформенного десктопа это AvaloniaUI. Но и они слегка сыроваты.
Получается, что рекламируемая мультиплатформенность в Xamarin Forms / MAUI пока не очень (или не про то)? Я пробовал гуглить, но как-то очень все туманно расписано про этот момент.
Просто на данный момент мультиплатформенность в Xamarin Forms/MAUI рекламируется, но в реальности не особо существует. Это всё ещё только в стадии превью/пререлиза. И то, что лично мне удалось немного пощупать, тоже было довольно сыровато.
То есть в перспективе да, выглядит интересно. Что из этого получится в итоге это отдельный большой вопрос. Silverlight тоже в своё время интересно выглядел.
Для совсем простых приложений (типа одно окно с графиком) мне фреймворк Eto.Forms понравился.
Мне нравится в какую сторону развивается платфмора .NET и язык C#. Пусть они будут высокопроизводительными, несмотря на потенциальную сложность. Для тех, кто не хочет въезжать, существуют Java, Go и другие альтернативы.
AOT для Blazor приложений дает ощутимый прирост скорости работы. Иногда в несколько раз.
stackalloc
А появилась ли возможность вставлять массивы фиксированного размера в структуры и классы как поля by value? Что-то вроде
struct Vec3 {
public float[3] coords;
}
Такая возможность давно была (но только для примитивных типов и только в небезопасном коде).
Интересно, откуда такие ограничения. По сути, пример выше эквивалентен чему-то вроде
struct Vec3 {
public float coord0;
public float coord1;
public float coord2;
}
но отдельными полями можно, а массивом нельзя.
но отдельными полями можно, а массивом нельзя.
Можно, причём чуть ли не с первой версии языка.
Добрый день. Я не очень в теме, но чисто формально. Есть nanoframework, есть старый MicroFramework. По сути обёртки над низкоуровневым кодом. Но добавили nativememory... Гипотетически теперь на c# можно писать наивный код под микроконтроллеры?
Или я немного не понимаю схемы работы....
Это же не только для оптимизации можно же использовать
Если честно, то сложно представить себе ситуацию, продемонстрированную в примере. Но будем иметь в виду.
Да не так, чтобы очень сложно. Если пытаться сделать сериализацию моделей того же Entity Framework, которые частенько содержат циклические ссылки на связанные модели, то в .Net 5 получим exception.
Хотя сама сериализация чистых моделей - не "true way".
Что нового в .NET 6?