В этом году .NET Conf подходит к концу, и мы наконец-то познакомились с общедоступной версией .NET 5 и C # 9, которые теперь широко доступны и могут использоваться разработчиками, использующими как минимум Visual Studio версии 16.8, которая поставляется в комплекте со всеми необходимыми компонентами для создания библиотек и приложений с использованием этих двух новых технологий.

Несмотря на весь ажиотаж вокруг всего этого, я видел, как многие разработчики в Twitter и на других каналах чувствовали себя обделенными. Причина заключалась в том, что они думали, что не смогут воспользоваться ни одним из этих новых инструментов на старых платформах, таких как UWP, Xamarin и всех других целевых объектах, которые не могут использовать ни .NET 5, ни какой-либо API, требующий больше, чем поддержка .NET Standard 2.0.

Хотя это правда, что если мы работаем с фреймворком, который не может просто обновиться до .NET 5, мы не сможем пользоваться многими замечательными функциями, которые новая версия среды выполнения и BCL привносят в нашу таблицу, то же самое На самом деле это не так для C # 9. По крайней мере, не для всех функций C # 9. Многие из них (большинство?) Могут фактически использоваться каждым разработчиком независимо от используемой среды выполнения - единственное требование - иметь доступ к последней версии компилятора Roslyn, такой как та, которая включена в Visual Studio 16.8!

Почему это работает? Поддерживается?

Немного упрощая, мы можем разделить функции каждой новой версии C # на две категории: те, которые требуют надлежащей поддержки времени выполнения для работы, и те, которые в основном являются просто синтаксическим сахаром. . Конечно, мы не сможем перенести какие-либо новые функции из первой категории в более старые среды выполнения, поскольку в этом случае компилятор C # просто откажется работать. И в этом есть смысл, поскольку мы все равно не сможем запустить этот код. Эта категория включает в себя такие функции, как методы интерфейса по умолчанию из C # 8 и ковариантные возвращаемые типы в C # 9. Вместо этого вторая категория немного более интересна: все функции C #, относящиеся к этой категории, упрощают работу разработчиков ( такие как новые типы записей в C # 9), но в результате получается код IL (язык байт-кода .NET), который без проблем может выполняться старыми средами выполнения. В конце концов, эти функции больше связаны с изменением того, как в конечном итоге выполняется данный двоичный файл, но по большей части они не вводят ничего нового на уровне IL. Что для нас здорово - это значит, что мы можем перебросить их!

Уровень 1: что работает прямо из коробки

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

Благодаря этому мы уже сможем использовать все функции C # 9, которые не требуют поддержки среды выполнения или дополнительных типов - а их довольно много!

* В некоторых старых платформах, таких как UWP, указатели на функции по-прежнему будут поддерживаться (включая возможность указать явное соглашение о вызовах), но использование ключевого слова unmanaged для объявления указателя функции с использованием соглашение о вызовах по умолчанию для используемой платформы может не поддерживаться.

А как насчет ссылочных типов, допускающих значение NULL?

Конечно, ссылочные типы, допускающие значение NULL, являются отличным дополнением к C # 8, поскольку они значительно упрощают предотвращение всех этих неприятных исключений с нулевыми ссылками. Они работают нормально, но они скрыты за флагом компилятора, который по умолчанию отключен, поэтому нам просто нужна еще одна строка в наших файлах .csproj, чтобы включить их:

Это позволит поддерживать их в стандартных библиотеках .NET и ряде других целей. Одно небольшое предостережение для тех из вас, кто работает с UWP: чтобы использовать эту функцию, нам также необходимо вручную аннотировать каждый из наших файлов .cs с помощью конкретной директивы компилятора. Нам просто нужно добавить эту строку в начало всех наших файлов, и мы также сможем использовать там аннотации, допускающие значение NULL:

Хотя использование обнуляемых ссылочных типов в нашем собственном коде будет работать нормально, наряду со статическим анализом потока на базе Roslyn, мы не получим преимуществ от аннотированных API в BCL, поскольку большая часть этой работы была сделана только в .NET 5. Из-за этого нам нужно будет соблюдать осторожность, чтобы сохранить правильные аннотации при передаче значений во все эти методы и из них.

В отношении ссылочных типов, допускающих значение NULL, следует учитывать еще один момент: не все атрибуты доступны в .NET Standard 2.0 и других подобных средах выполнения. Для этого перейдем к следующему разделу.

Уровень 2: для чего мы можем включить поддержку

Хотя в C # 8 и 9 у нас еще остались некоторые функции, которые могут работать с нашими более старыми целями, эти оставшиеся потребуют некоторой дополнительной заботы, чтобы сделать компилятор Roslyn счастливым. В частности, это функции, которые имеют обратную совместимость, но требуют наличия в проекте определенных типов или атрибутов. Хорошей новостью является то, что если нам не хватает библиотек, мы можем просто сослаться на них из NuGet, а если мы пропустим типы, мы можем буквально просто скопировать их из источника CoreCLR на GitHub и вставить в наш проект! Компилятору C # на самом деле не нужно, чтобы эти типы были встроенными - до тех пор, пока он может найти их в нужном месте, этого будет достаточно для успешной сборки нашего кода. Итак, давайте рассмотрим все оставшиеся функции одну за другой!

Записи C # 9 и свойства только для инициализации

Новые типы записей, представленные в C # 9, могут значительно сократить объем стандартного кода, необходимого для реализации простых моделей, но они являются одной из функций, требующих некоторой помощи для правильной работы на старых целевых объектах. В частности, компилятору C # потребуется специальный тип IsExternalInit, который должен присутствовать в нашем проекте. Чтобы включить поддержку для этого, мы можем просто скопировать следующий код в любое место вашего нашего файла, а затем попробовать построить снова!

C # 9 атрибут [SkipLocalsInit]

Этот новый атрибут полезен в сценариях, критичных к производительности, и указывает компилятору не выдавать флаг .locals init для переменных в методе. Это позволяет избежать ненужных инициализаций нулями переменных и повысить производительность. Атрибут недоступен в старых средах выполнения, но, как и в случае с записями, нам просто нужно скопировать его в наш проект, а затем использовать в обычном режиме. Roslyn распознает его и будет использовать без проблем, поскольку он просто проверяет имя типа и пространство имен, а не сборку, которой на самом деле принадлежит тип. Вот:

C # 8 асинхронные потоки и одноразовые ресурсы

Эта функция основана на типах IAsyncEnumerable ‹T› и IAsyncDisposable и позволяет нам писать асинхронные перечислители для использования нового await foreach и реализовать асинхронные методы удаления, чтобы включить новый синтаксис await using. Все, что нам нужно сделать в этом случае, - это добавить ссылку на пакет NuGet Microsoft.Bcl.AsyncInterfaces, который включает все эти необходимые типы. Как только у нас будет ссылка на этот пакет, мы сможем использовать все эти новые API без каких-либо проблем:

Атрибуты анализа потока

Вы могли заметить, что, особенно в отношении ссылочных типов, допускающих значение NULL, не все необходимые атрибуты для украшения полей, свойств, методов и других членов доступны, особенно при работе с .NET Standard 2.0 и ниже. Это такие атрибуты, как [MaybeNullWhen], [NotNullWhen] и другие, подробно описанные в документации, которые обеспечивают лучший статический анализ, поскольку они могут дать компилятору больше информации о том, как наш код взаимодействует со значениями. К счастью, мы можем просто перенести все это из источника CoreCLR, а затем использовать их так же, как если бы они уже были доступны в структуре, с которой мы работаем. Вот суть со всеми доступными в настоящее время атрибутами, готовыми к использованию - мы можем просто вставить этот файл в наш проект и начать использовать их там, где нам нужно:

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

Обязательно знайте, что как разработчик UWP (так что пока я застрял на .NET Standard 2.0), я могу сказать, что есть много других разработчиков, таких как я, которые действительно ценят время и усилия, вложенные в поддержку библиотек, которые все из нас все еще на «старых» технологиях могут использовать.

Удачного кодирования! 🚀