Posted on 10. October 2023

C# Dev Kit – Now Generally Available

C# Dev Kit - тепер загальнодоступне

Радо повідомляємо про загальну доступність C# Dev Kit, розширення коду Visual Studio, яке покращує процес розробки на C#  для Linux, macOS та Windows.

C# Dev Kit

Подяка зусиллям спільноти!

 

З моменту першого попереднього перегляду в червні було отримано як кількісні дані, так і неоціненні відгуки спільноти, які допомогли сформувати цей продукт. Було вирішено близько 350 проблем, про які переважно повідомляла спільнота. Ці вдосконалення охоплюють як покращення якості, так і роз'яснення сценаріїв. Завдяки активній участі користувачів було зроблено понад 300 цілеспрямованих удосконалень, що дозволило зробити розширення більш надійним і безпечним. Ці спільні зусилля стали вирішальними у рішенні перейти від попереднього перегляду до загальної доступності та розпочати офіційну підтримку для підписників Visual Studio.

Що таке C# Dev Kit?

C# Dev Kit використовує основні можливості мови C# і надає розробникам додаткові можливості для продуктивної роботи. Хоча ці основні функції вже стали загальнодоступними, додаткові можливості, що підтримують .NET MAUI та Unity, все ще перебувають у стадії попереднього перегляду, використовуючи C# Dev Kit. Ці розширення продовжують отримувати відгуки та покращувати робочі процеси розробки для MAUI та Unity у VS Code.

Переглядайте корисну інформацію у цьому відео!

Майбутні плани щодо C# Dev Kit

Сьогоднішній офіційний запуск - це лише початок, оскільки надалі планується випускати оновлення розширення щомісяця, прислухаючись до відгуків користувачів та працюючи над покращенням продуктивності, надійності та додаванням нових функцій для підтримки розробки на C# у VS Code. Якщо ви бажаєте отримувати оновлення раніше, підпишіться на канал попередніх випусків, де будуть публікуватися виправлення та анонси нових функцій в міру їх розробки.

Будь ласка, діліться своїми відгуками, повідомляйте про нові проблеми за допомогою коду VS або здійснюйте пошук наявних удосконалень і проблем, а також ставте "великий палець вгору" або надавайте додатковий контекст проблеми, щоб допомогти розставити пріоритети.

Дізнатися більше

Якщо ви хочете дізнатися більше про C# Dev Kit, ви можете відвідати кілька чудових сесій на Ignite та .NET Conf у листопаді або ознайомитися з оновленою документацією C# VS Code та інструкціями з початку роботи. Спробуйте нове середовище C# з C# Dev Kit вже сьогодні!

 

Встановити C# Dev Kit



Posted on 23. January 2023

Анонс .NET Community Toolkit 8.1! Покращені, швидші генератори вихідного коду MVVM, підтримка .NET 7 та багато іншого!



Анонс .NET Community Toolkit 8.1! Покращені, швидші генератори вихідного коду MVVM, підтримка .NET 7 та багато іншого!

З радістю повідомляємо про офіційний запуск версії 8.1 інструментарію .NET Community Toolkit! Ця нова версія містить нові функції, які були дуже запитувані, виправлені помилки та значні покращення продуктивності генераторів вихідного коду MVVM Toolkit, щоб покращити UX для розробників при їх використанні як ніколи!

 

Що входить до складу .NET Community Toolkit? 

Як і в інших анонсах, розпочнемо з невеликого огляду того, що входить до складу інструментарію .NET Community Toolkit. Він складається з декількох незалежних бібліотек:

Ці бібліотеки також широко використовуються у деяких програмах для роботи з поштою, які входять до складу Windows, зокрема у Microsoft Store та програмі "Фотографії"! 

Для більш детальної інформації про історію .NET Community Toolkit, ось посилання на попередній пост з анонсом 8.0.0.

 

Нижче наведено перелік основних змін, які включено до нового випуску 8.1 інструментарію .NET Community Toolkit.

Користувацькі атрибути для [ObservableProperty] (Властивість, що спостерігається) 

Як вже згадувалося у блозі з анонсом 8.1.0 Preview 1, однією з найбільш запитуваних функцій (див. #208, #217, #228) для генератора вихідного коду MVVM Toolkit була підтримка використання користувацьких атрибутів для [ObservableProperty]. Було запропоновано декілька проєктів для підтримки цієї можливості, і зрештою було вирішено використати наявну властивість: синтаксис у C#, який дозволяє розробникам позначати атрибути для поширення їх на згенеровані властивості. Це дає кілька переваг:

1. Він використовує вбудований синтаксис C#, що робить властивість "рідною" і не потребує додаткових атрибутів

2. Це розв'язує проблему анотування атрибутів, які можуть бути спрямовані лише на властивості, а не на поля

 

Тобто, у MVVM Toolkit 8.1 тепер підтримується наступний сценарій:

Це призведе до створення наступної властивості за замовчуванням:

Ви можете побачити, як згенерована властивість має два атрибути, які було вказано! Таким чином, ви можете гнучко створювати анотації для згенерованих властивостей, використовуючи вбудований синтаксис C# і без обмежень на типи атрибутів, які підтримуються цією функцією. 

Примітка: згенерований код дещо відрізняється і включає додаткові оптимізації продуктивності, не показані тут.

Ви можете знайти всі документи про нові генератори коду тут, а якщо ви віддаєте перевагу відеоверсії, Джеймс Монтемагно також зняв кілька відео про них, наприклад, це.

Аналізатори MVVM Toolkit 

Цей випуск MVVM Toolkit також є першим, в якому представлені спеціальні аналізатори, що допомагають розробникам використовувати MVVM Toolkit найкращим чином. Тобто, MVVM Toolkit більше не буде просто видавати діагностику для функцій, які використовуються неправильно (тобто таким чином, що призведе до помилки), тепер він також буде показувати рекомендації щодо покращення коду та уникнення поширених помилок! 

Перший аналізатор розглядає поширену помилку при використанні атрибута [ObservableProperty]. Розглянемо такий приклад:

Розробники неодноразово стикалися з такою проблемою, коли випадково присвоювали значення полю (а не згенерованій властивості), а потім стикалися з тим, що інтерфейс не відображав зміни, без чіткого пояснення, чому це сталося. Новий аналізатор допоможе у цих випадках, і він позначатиме всі присвоєння полів, що підтримують видиму властивість, показуючи діагностику, яка запропонує посилатися на створену властивість замість неї. Більше жодних загадкових сповіщень про відсутність властивості!

Другий новий аналізатор має на меті допомогти зменшити розмір двійкових файлів у додатках, що використовують MVVM Toolkit. Як вже згадувалося в анонсі 8.0.0, MVVM Toolkit включає декілька атрибутів (таких як [ObservableObject]), які дозволяють генераторам вставляти весь код, необхідний для реалізації інтерфейсів INotifyPropertyChanged та INotifyPropertyChanging (опціонально з додатковими допоміжними засобами) у вже наявні класи. Це особливо корисно у випадках, коли клас вже успадковується від іншого типу, ніж ObservableObject: ви можете використовувати атрибут і мати доступ до тих самих допоміжних функцій, не переробляючи логіку самостійно.

Однак це стосується лише тих випадків, коли успадкування неможливе: якщо ж це не так, краще просто успадкувати від ObservableObject і скористатися перевагами зменшеного розміру двійкового файлу, оскільки компілятору не доведеться копіювати ті самі допоміжні функції знову і знову у кожному типі. Розглянемо цей приклад:

Тут MyViewModel не успадковує від жодного типу, тому їй краще успадкувати від ObservableObject, а не використовувати атрибут [ObservableObject], щоб отримати вигоду від покращення бінарного розміру. Новий аналізатор позначатиме всі сценарії, подібні до цього, пропонуючи використовувати успадкування. Це особливо допоможе новачкам, які можуть не розуміти нюансів двох різних підходів і не знати, як зробити вибір. У таких випадках аналізатор тепер буде поруч, щоб допомогти.

Оптимізація генератора вихідних кодів MVVM Toolkit 

Як вже згадувалося, цей новий випуск також містить значні оптимізації продуктивності MVVM Toolkit, щоб ще більше покращити UX для розробників, особливо при роботі над дуже великими проєктами.

Ось лише деякі з покращень у цьому напрямку:

1. Додано багатоцільове орієнтування для Roslyn 4.3 (#428, #462): генератори вихідних кодів MVVM Toolkit тепер використовуватимуть орієнтир на Roslyn 4.3, якщо він підтримується, щоб вони могли підключатися до деяких оптимізованих API, якщо хост це підтримує. Ця функція автоматично вмикається при зверненні до MVVM Toolkit.

 

2. Використання ForAttributeWithMetadataName<T> (#436): було переключено генератори на новий високорівневий API Roslyn для зіставлення атрибутів, що значно покращує продуктивність генераторів, які спрацьовують за певними атрибутами. Наприклад, [ObservableProperty] тепер використовує цю можливість.

3. Перенесено діагностику в діагностичні аналізатори (#433, #434): було перенесено майже всю діагностику в діагностичні аналізатори, які виконуються поза процесом і незалежно від генераторів вихідних кодів. Це значно зменшує накладні витрати при наборі коду, оскільки вся логіка діагностики тепер виконується в окремому процесі й не може сповільнювати роботу IntelliSense.

4. Припинено використання символів у інкрементних провайдерах (#435): було оновлено всі інкрементні провайдери, щоб більше не поширювати символи. Це може зменшити використання пам'яті, оскільки розповсюдження символів може призвести до того, що Roslyn надмірно викорінюватиме об'єкти компіляції.

5. Більше оптимізацій продуктивності (#447, #460, #469, #487, #489): було переглянуто всі інкрементальні моделі та інкрементальні конвеєри, що значно покращило продуктивність та зменшило виділення пам'яті.

Розширення месенджерів IObservable<T> 

 

Ще однією запитуваною функцією, особливо розробниками, які інтенсивно використовують API у стилі Reactive у своїх додатках, була можливість об'єднати функціональність API месенджерів у MVVM Toolkit. Тепер це підтримується завдяки новим розширенням IObservable<T> для інтерфейсу IMessenger. Їх можна використовувати наступним чином:

 

... Ось і все! Це розширення створить об'єкт IObservable<T>, який можна використовувати для підписки на повідомлення і динамічної реакції на них. Також підтримується вказівка різних токенів за допомогою окремих перевантажень. Ось ще один приклад, що демонструє наскрізне використання нового API:

Підтримка .NET 7 та C# 11 

Цей новий випуск .NET Community Toolkit також додає .NET 7 TFM до пакета HighPerformance і містить кілька змін, що дозволяють скористатися новими можливостями мови C# 11, зокрема, посиланнями на поля (ref fields).

Наступні типи більше не знаходяться у попередньому перегляді й були оновлені для використання нових правил безпеки ref-полів:

Приклад, де вони можуть бути використані, наведено нижче:

Тобто, тип NullableRef<T> фактично дозволяє методу мати параметр out ref T, який не може бути виражений в C# іншим чином. Планується також розширити поверхню API цих типів у майбутньому, щоб ці типи могли стати простою у використанні альтернативою GC-ref арифметиці з використанням типу Unsafe, яка також може бути візуально більш схожою на традиційну арифметику з вказівниками.

 

Крім того, всі типи ref-структур, яких ще не було у попередньому перегляді, було оновлено для внутрішнього використання полів ref для покращення продуктивності. До них відносяться:

Інші зміни 

У цьому новому випуску набагато більше змін!

Ви можете переглянути повний список змін на сторінці релізу на GitHub.

Розпочинайте прямо зараз! 

Ви можете знайти весь вихідний код у репозиторії GitHub, деякі рукописні документи на MS learn та повні посилання на API на вебсайті браузера .NET API. Якщо ви хочете зробити свій внесок, не соромтеся повідомляти про проблеми або ділитися з нами своїм досвідом! Щоб стежити за обговоренням у Twitter, використовуйте хештег #CommunityToolkit. Всі ваші відгуки дуже допомагають формувати напрямок розвитку цих бібліотек, тому обов'язково діліться ними!

Більше ресурсів 

Якщо ви хочете дізнатися більше про MVVM Toolkit, ви також можете переглянути це відео з нещодавньої конференції .NET Conf 2022, де показано, як можна використовувати MVVM Toolkit, новий генератор коду та всі нові функції в 8.1:

Існує ціла екосистема доступних Toolkit'ів з безліччю корисних API для створення .NET-додатків! Дізнайтеся більше про них у документації MS learn!

 

Вдалого кодування!

Source



Posted on 19. March 2020

Асинхронное объединение ValueTask в .NET 5

Функция async / await в C # произвела революцию в том, как разработчики, нацеленные на .NET, пишут асинхронный код. Прибавьте немного async и await, измените, некоторые типы возвращаемых данных на задачи, и вы получите асинхронную реализацию. Теоретически.

На практике, очевидно, я преувеличивал легкость, с которой кодовая база может быть сделана полностью асинхронной, как и со многими задачами в разработке программного обеспечения, загвоздки часто в деталях. Одной из таких «загвоздок», с которыми, вероятно, знакомы разработчики .NET, ориентированные на производительность, является объект конечного автомата, который позволяет асинхронному методу выполнить свою магию.

Распределения и конечные автоматы

Когда вы пишете асинхронный метод в C #, компилятор переписывает это в конечный автомат, где большая часть вашего кода в вашем асинхронном методе перемещается в MoveNext сгенерированного компилятором типа (структура в сборках Release),  и с этим MoveNext  завален переходами и метками, которые позволяют методу приостановить и резюмировать в пункты  await. К незавершенным задачам await подключено продолжение (обратный вызов), которое при окончательном завершении задачи вызывает метод MoveNext и переходит к месту, где функция была приостановлена. Для того чтобы локальные переменные могли поддерживать свое состояние через эти выходы и повторные входы метода, соответствующие «локальные объекты» переписываются компилятором в поля типа конечного автомата. И для того, чтобы этот конечный автомат как структура сохранялся в тех же самых приостановках, он должен быть перемещен в кучу.

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

В .NET Framework, момент Task или  ValueTask возвращение асинхронного метода (как общего, так и не универсального) приостанавливается впервые, происходит несколько выделений:

1. Структура конечного автомата копируется в кучу через стандартный бокс времени выполнения; каждый конечный автомат реализует IAsyncStateMachine  интерфейс, среда выполнения буквально приводит структуру к этому интерфейсу.

2. Среда выполнения фиксирует текущий ExecutionContext а затем выделяет объект (он называет это «бегун»), который он использует для хранения обоих конечных автоматов в штучной упаковке и ExecutionContext  (обратите внимание, что в .NET Framework, регистрация ExecutionContext  если это не значение по умолчанию, это также приводит к одному или нескольким выделениям).

3. Среда выполнения выделяет Action , который указывает на метод в этом объекте запуска, потому что шаблон ожидающего требует Action  которые будут переданы ожидающему методу {Unsafe}OnCompleted; при вызове Action будет использовать ExecutionContext для вызова метода MoveNext на конечном компьютере.

4. Среда выполнения выделяет объект Task, который будет завершен после завершения асинхронного метода и это возвращается из асинхронного метода к его синхронному вызывающему (если асинхронный метод набран для возврата ValueTask, структура ValueTask просто оборачивается вокруг объекта Task).

Это как минимум четыре средства, когда асинхронный метод в первый раз приостанавливается. Кроме того, каждый последующий раз асинхронный метод приостанавливается, если мы окажемся с нестандартным ExecutionContext  (например, он переносит состояние для AsyncLocal<T>),  среда выполнения перераспределяет это в запускающийся объект, затем перераспределяет действие, которое указывает на него (потому что делегаты неизменны), каждый раз выполняя минимум два дополнительных выделения, когда асинхронный метод приостанавливается после первого раза. Вот простое повторение этого в Visual Studio с правым окном, в котором отображаются распределения в соответствии с инструментом отслеживания распределения объектов .NET:

Это было значительно улучшено, в .NET Core, особенно с .NET Core 2.1. Когда асинхронный метод приостанавливается, выделяется Task. Но это не базовый тип Task или Task<TResult>. Структура конечного автомата хранится в строго типизированном поле для этого производного типа, устранение необходимости в отдельном распределении бокса. Этот тип также имеет поле для захваченного ExecutionContext (который является неизменным в .NET Core, это означает что захват никогда не выделяется), нам не нужен отдельный объект ранера. И у среды выполнения теперь есть специальные пути кода, которые поддерживают передачу этого типа AsyncStateMachineBox<TStateMachine> напрямую всем ожидающим, о которых среда выполнения знает, это означает, что пока асинхронный метод ожидает только TaskTask<TResult>ValueTask или ValueTask<TResult> (напрямую или через их аналоги ConfigureAwait), ему вообще не нужно выделять Action. Затем, поскольку у нас есть прямой доступ к полю ExecutionContext, последующие приостановки не требуют выделения нового участника (участники полностью отсутствуют), это также означает, что даже если нам нужно было распределить действие, нам не нужно его перераспределять. Это значит, тогда как в .NET Framework у нас есть как минимум четыре выделения для первой приостановки и часто по крайней мере два распределения для каждой последующей приостановки, в .NET Core у нас есть одно распределение для первого приостановления (наихудший случай два, если используются пользовательские ожидающие), и это все. Другие изменения, такие как переписывание инфраструктуры очередей ThreadPool, также значительно сократили распределение.

 

Это изменение оказало очень ощутимое влияние на производительность (и, как оказалось, не только на производительность; оно также очень полезно для отладки), и мы все можем радоваться удалению ненужных ассигнований. Однако, как уже было отмечено, одно распределение все еще остается, когда асинхронный метод завершается асинхронно. Но ... что если мы тоже сможем избавиться от этого?  Что если бы мы могли сделать так, чтобы вызов асинхронного метода имел (амортизировался) накладные расходы при нулевом распределении независимо от того, завершился он синхронно или асинхронно?

ValueTask

ValueTask<TResult> был введен в период .NET Core 1.0, чтобы помочь разработчикам избежать выделения ресурсов,  когда асинхронные методы завершаются синхронно. Это была относительно простая структура, представляющая различаемое объединение между TResult и Task<TResult>. При использовании в качестве типа результата асинхронного метода, если вызов асинхронного метода возвращается синхронно, независимо от значения результата TResult, метод требует нулевого распределения накладных расходов: конечный автомат не нужно перемещать в кучу, и нет необходимости в задании Task<TResult> для результата; значение результата просто сохраняется в поле TResult возвращенной ValueTask<TResult>. Однако, если асинхронный метод завершается асинхронно, среда выполнения возвращается к поведению так же, как и в случае с задачей Task<TResult>: он создает единственную задачу AsyncStateMachineBox<TStateMachine>, который затем возвращается в структуру ValueTask<TResult>.

В .NET Core 2.1 мы представили интерфейс IValueTaskSource<TResult>, наряду с неуниверсальными аналогами ValueTask и IValueTaskSource. Мы также сделали ValueTask<TResult> способным хранить не только TResult, но и Task<TResult>, но также IValueTaskSource<TResult> (то же самое для неуниверсального ValueTask, который, может хранить Task или IValueTaskSource). Этот продвинутый интерфейс позволяет предприимчивому разработчику написать свое собственное резервное хранилище для задачи,  и они могут делать это таким образом, чтобы повторно использовать этот объект хранилища резервных копий для нескольких не параллельных операций (намного больше информации об этом доступно в этом посте). Например, Socket обычно используется не более чем для одной операции приема и одна операция отправки за раз. Socket  был изменен для хранения повторно используемого / сбрасываемого IValueTaskSource<int> для каждого направления и каждой последующей операции чтения или записи что завершает и асинхронно раздает ValueTask<int>, поддерживаемый соответствующим общим экземпляром. Это означает, что в подавляющем большинстве случаев методы ReceiveAsync/SendAsync на основе ValueTask<int> в Socket  в конечном итоге не выделяются, независимо от того, выполняются они синхронно или асинхронно.  Несколько типов получили эту обработку, но только в тех случаях, где это будет действенно.

Таким образом, в .NET Core 2.1 были добавлены несколько реализаций в ключевых областях, таких как System.Net.SocketsSystem.Threading.Channels и System.IO.Pipelines, но не намного дальше. Впоследствии мы ввели тип ManualResetValueTaskSource<TResult>, чтобы упростить такие реализации, и в результате было добавлено больше реализаций этих интерфейсов в .NET Core 3.0, а также в .NET 5, хотя в основном внутри различных компонентов, таких как System.Net.Http.

Улучшения в .NET 5

В .NET 5 мы дальше экспериментируем с этой оптимизацией. С .NET 5 Preview 1, если до запуска вашего процесса для переменной среды DOTNET_SYSTEM_THREADING_POOLASYNCVALUETASKS установлено значение true  или 1, среда выполнения будет использовать объекты конечного автомата, которые реализуют интерфейсы IValueTaskSource и IValueTaskSource<TResult>. Он будет объединять объекты, которые создает, для возвращенных экземпляров из асинхронных методов async ValueTask  или async ValueTask<TResult>. Итак, если, как и в предыдущем примере, вы повторно вызываете один и тот же метод и ожидаете результата, каждый раз, когда вы в конечном итоге получите ValueTask, который оборачивает один и тот же объект, просто сбрасывайте его каждый раз, чтобы позволить отслеживать другое выполнение. Магия.


Почему он не включен по умолчанию прямо сейчас? Две основные причины:

1. Объединение не является бесплатным. Разработчик может оптимизировать свой код различными способами. Один из них - просто улучшить код, чтобы больше не нуждаться в выделении; с точки зрения производительности это, как правило, очень низкий риск. Другой - повторно использовать уже доступный объект, например, добавив дополнительное поле к существующему объекту с аналогичным сроком службы; это все еще требует более подробного анализа производительности. Затем приходит объединение. Оно может быть очень полезным, когда создание объединяемой вещи очень ценно; хорошим примером этого является пул соединений HTTPS, где стоимость установления нового безопасного соединения, как правило, на несколько порядков дороже, чем доступ к нему даже в самой наивной структуре пулов данных. Более спорная форма объединения - это когда пул предназначен для дешевых объектов с целью избежать затрат на сборку мусора. Используя такой пул, разработчик делает ставку на то, что он может реализовать собственный распределитель (который действительно является пулом), который лучше, чем универсальный распределитель GC. Победить в сборной нетривиально. Но разработчик может это сделать, учитывая знания, которые они имеют в своем конкретном сценарии. Например, .NET GC очень хорошо умеет эффективно собирать недолговечные объекты, которые становятся коллекционными в поколении 0, и попытка объединения таких объектов может легко сделать программу более дорогой (даже если это выглядит хорошо на микробенчмарке, сфокусированном на измерении распределения). Но если вы знаете, что ваши объекты, вероятно, выживут в gen0, например, если они используются для представления потенциально асинхронных операций с большой задержкой, вполне возможно, что пользовательский пул может сбрить некоторые накладные расходы. Мы еще не сделали этот асинхронный пул async ValueTask по умолчанию, потому что, хотя он хорошо выглядит на микробенчмарках, мы не уверены, что это действительно значительное улучшение рабочих нагрузок в реальном мире.

2. ValueTasks имеют ограничения. Типы Task  и Task<TResult> были разработаны, чтобы быть очень надежными. Вы можете их кешировать. Вы можете ждать их любое количество. Они поддерживают несколько продолжений. Они потокобезопасны, с любым количеством потоков, способных одновременно регистрировать продолжения. И в дополнение к ожиданию и поддержке асинхронных уведомлений о завершении, они также поддерживают модель блокировки, при этом синхронные абоненты могут ожидать получения результата. Ничто из этого не относится к ValueTask и ValueTask<TResult>. Потому что они могут быть поддержаны сбрасываемыми экземплярами IValueTaskSource, вы не должны их кэшировать (то, что они переносят, может быть использовано повторно) и не ждать их несколько раз. Вы не должны пытаться зарегистрировать несколько продолжений (после первого завершения объект может попытаться сбросить себя для другой операции), будь то одновременно или нет. И вы не должны пытаться блокировать ожидание их завершения (реализации IValueTaskSource не должны предоставлять такую семантику). Пока вызывающие абоненты напрямую ожидают результата вызова метода, который возвращает  ValueTask или ValueTask<TResult>, все должно работать хорошо, но как только кто-то сходит с этого золотого пути, все может быстро измениться; это может означать получение исключений или коррупцию в процессе. Кроме того, эти сложности обычно проявляются только тогда, когда ValueTask или ValueTask<TResult> переносят реализацию IValueTaskSource; когда они переносят Task, вещи обычно «просто работают», так как ValueTask наследует надежность Task, и когда они оборачивают необработанное значение результата, ограничения технически вообще не применяются. И это означает, что, переключая асинхронные методы async ValueTask с поддержкой Task, вместо поддержки этих объединенных реализаций IValueTaskSource, мы могли бы выявлять скрытые ошибки в приложении разработчика, либо напрямую, либо через библиотеки, которые они используют. Предстоящий выпуск Roslyn Analyzers будет включать в себя анализатор, который должен помочь найти большинство нецелевых использований.

Призыв к действию

Если у вас есть приложение, которому, по вашему мнению, будет полезно это объединение, мы будем рады получить от вас сообщение. Скачивайте .NET 5 Preview 1. Попробуйте включить эту функцию. Что-нибудь сломается, в вашем коде, или в другой библиотеке, или в самом .NET. Или вы увидите значительные изменения в производительности, такие как пропускная способность или задержка, или рабочий набор, или что-то еще интересное. Обратите внимание, что изменение касается только асинхронных методов async ValueTask и async ValueTask<TResult>, поэтому, если у вас есть async Task или async Task<TResult>, вам также может потребоваться сначала изменить их, чтобы использовать их эквиваленты ValueTask.

Выпуск dotnet / runtime # 13633 отбражает виденье того, что мы должны делать с этой функцией для .NET 5, и Microsoft хочет услышать ваш фидбек; команда будет рада, если вы опубликуете какие-либо мысли или результаты.


Источник



Posted on 28. January 2019

Больше возможностей с шаблонами в C# 8.0

Больше возможностей с образцами в C# 8.0

Вышел второй предварительный просмотр Visual Studio 2019! Наряду с ним были разработаны дополнительные C# 8.0 функции. В основном в данной статье речь пойдет о новых образцах, хотя в конце также будут рассмотрены другие новости и изменения.

Еще больше образцов

Когда в C# 7.0 была добавлена поддержка образцов, Microsoft объявили, что собираются добавить еще больше образцов в большем количестве знаков. Это время настало! Добавляются так называемые рекурсивные образцы, а также более компактная форма оператора switch выражений, называемых (как Вы уже догадались) switch выражениями.

Вот простой пример образцов C# 7.0:

class Point
{
    public int X { get; }
    public int Y { get; }
    public Point(int x, int y) => (X, Y) = (x, y);
    public void Deconstruct(out int x, out int y) => (x, y) = (X, Y);
}

static string Display(object o)
{
    switch (o)
    {
        case Point p when p.X == 0 && p.Y == 0:
            return "origin";
        case Point p:
            return $"({p.X}, {p.Y})";
        default:
            return "unknown";
    }
}

Switch выражения

Следует отметить, что многие switch выражения на самом деле не выполняют в case основах ничего особенного. Часто они просто создают значение, либо присваивая его переменной, либо возвращая его (как указано выше). Во всех этих случаях switch выражение выглядит довольно неуклюже и напоминает язык пятидесятилетней давности, очень церемонный и громоздкий.

Поэтому пришло время добавить форму выражения switch. См. пример:

static string Display(object o)
{
    return o switch
    {
        Point p when p.X == 0 && p.Y == 0 => "origin",
        Point p                           => $"({p.X}, {p.Y})",
        _                                 => "unknown"
    };
}

Ниже приведены отличия от switch операторов:

  • Ключевое слово switch – это «infix» между проверенным значением и {...} списком случаев. Это делает данное слово более совместимым с другими выражениями, а также позволяет отличить его визуально от switch оператора.
  • Ключевое слово case и «:» символ для краткости были заменены лямбда-стрелкой =>.
  • Default значение для краткости было заменено _ шаблоном сброса.
  • Атрибуты bodies – это выражения! Результат выбранного атрибута становится результатом switch выражения.

Поскольку выражение должно иметь значение или выдавать исключение, то switch выражение, которое доходит до конца без совпадений, генерирует исключение. Компилятор предупреждает, когда это может произойти, но не заставляет останавливать все switch выражения с помощью catch-all сервиса: ведь Вы знаете лучше!

Теперь, когда Display метод состоит из только одного return оператора, можно упростить его до состояния выражения:

static string Display(object o) => o switch
    {
        Point p when p.X == 0 && p.Y == 0 => "origin",
        Point p                           => $"({p.X}, {p.Y})",
        _                                 => "unknown"
    };

Этот путь более лаконичный и понятный, ведь, как указано выше, именно краткость позволяет форматировать «табличным» способом switch, с шаблонами и телами на одной линии, где => выстроились друг под другом.

Microsoft планирует разрешить использование «,» запятой после последнего кейса в соответствии со всеми другими «разделенными запятыми списками в фигурных скобках» в C#, но во втором предварительном просмотре Visual Studio 2019 осуществить это пока нельзя.

Образцы свойств

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

Обратите внимание, что switch выражение использует Point p тип шаблона (дважды), а также when предложение для добавления дополнительных условий для первого case.

 В C# 8.0 Microsoft добавляет дополнительные необязательные элементы в тип образца, что позволяет самому образцу углубиться в значение сопоставляемого шаблона. Его можно сделать шаблоном свойства, добавив {...}, который содержит вложенные шаблоны, для применения к доступным свойствам или полям значения. Это позволяет переписать switch выражение следующим образом:

static string Display(object o) => o switch
{
    Point { X: 0, Y: 0 }         p => "origin",
    Point { X: var x, Y: var y } p => $"({x}, {y})",
    _                              => "unknown"
};
Оба случая все еще проверяют, что o является Point. В первом случае 0 шаблон константы применяется рекурсивно к X и Y свойствам переменной p, проверяя, есть ли у них это значение. Таким образом, when условие может быть исключено в данном и многих распространенных случаях.

Во втором случае var шаблон применяется к каждому из X и Y. Стоит напомнить, что выполнение var шаблона в C# 7.0 всегда завершается успешно и просто объявляет новую переменную для хранения значения. Таким образом, x и y содержат значения int для p.X и p.Y.

p никогда не используется, а значит, фактически его можно опустить и здесь:

Point { X: 0, Y: 0 }         => "origin",
    Point { X: var x, Y: var y } => $"({x}, {y})",
    _                            => "unknown"
То, что остается верным для всех типов образцов, включая образцы свойств, – это их ненулевое значение. Благодаря чему становится возможным использование «пустого» {} свойства образца в качестве компактного «ненулевого» шаблона. Например, запасной вариант можно заменить следующими двумя двумя кейсами:

{}                           => o.ToString(),
    null                         => "null"

Позиционные образцы

Образец свойств не совсем укорачивает второй Point кейс, но в данном случае можно сделать многое.

Обратите внимание, что у Point класса есть Deconstruct метод, так называемый deconstructor. В C# 7.0 деконструкторы позволяли деконструировать значение при присваивании, чтобы можно было написать код такого плана:

(int x, int y) = GetPoint(); // split up the Point according to its deconstructor
В C# 7.0 деконструкция с использованием образцов интегрирована не была. Это меняется с позиционными образцами, которые являются дополнительным способом расширения шаблонов типов в C# 8.0. Если совпавший тип является типом кортежа или у него есть деконструктор, то можно использовать позиционные шаблоны как компактный способ применения рекурсивных шаблонов без необходимости называть свойства:

static string Display(object o) => o switch
{
    Point(0, 0)         => "origin",
    Point(var x, var y) => $"({x}, {y})",
    _                   => "unknown"
};

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

Использование деконструкторов не всегда уместно. Их следует добавлять только к тем типам, где значения объяснимы и бесспорны. Например, для Point класса первое значение – X, а второе – Y очевидны, поэтому приведенное выше switch выражение интуитивно понятно и легко читается.

Образцы кортежей

Очень полезный особый случай позиционных образцов – их применение к кортежам. Если switch оператор применяется непосредственно к выражению кортежа, то разрешено опустить дополнительный набор скобок, как в switch (x, y, z) вместо switch ((x, y, z)).

Шаблоны кортежей отлично подходят для одновременного тестирования нескольких входных данных. Ниже приведена простая реализация стейт-машины:

static State ChangeState(State current, Transition transition, bool hasKey) =>
    (current, transition) switch
    {
        (Opened, Close)              => Closed,
        (Closed, Open)               => Opened,
        (Closed, Lock)   when hasKey => Locked,
        (Locked, Unlock) when hasKey => Closed,
        _ => throw new InvalidOperationException($"Invalid transition")
    };

Конечно, можно было бы добавить hasKey во включенный кортеж вместо использования when предложений, но это действительно вопрос вкуса:

static State ChangeState(State current, Transition transition, bool hasKey) =>
    (current, transition, hasKey) switch
    {
        (Opened, Close,  _)    => Closed,
        (Closed, Open,   _)    => Opened,
        (Closed, Lock,   true) => Locked,
        (Locked, Unlock, true) => Closed,
        _ => throw new InvalidOperationException($"Invalid transition")
    };

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

Другие функции C# 8.0 во Втором Предварительном Просмотре

Несмотря на то, что во втором предварительном просмотре VS 2019 основные функции для работы с образцами являются наиболее важными, есть несколько более мелких особенностей, которые не менее полезны и интересны. Ниже приведено краткое описание каждой.

Using объявления

В C# using операторы всегда приводят к вложенности, что может сильно раздражать и ухудшать читабельность. Для простых случаев, когда нужно, чтобы ресурс был очищен в конце области, теперь используются using объявления. Using объявления – это объявления локальных переменных с ключевым словом using в начале, где их содержимое располагается в конце текущего блока операторов. Так что вместо:

static void Main(string[] args)
{
    using (var options = Parse(args))
    {
        if (options["verbose"]) { WriteLine("Logging..."); }
        ...
    } // options disposed here
}

Можно написать следующее

static void Main(string[] args)
{
    using var options = Parse(args);
    if (options["verbose"]) { WriteLine("Logging..."); }

} // options disposed here
Disposable ref структуры

Ref Структуры были введены в C# 7.2, полезные функции которых были рассмотрены в других статьях. Стоит отметить, что они накладывают некоторые серьезные ограничения, такие как невозможность реализации интерфейсов. Ref структуры теперь доступны без реализации Idisposable интерфейса, просто с помощью Dispose метода.

Статические локальные функции

Чтобы убедиться, что выполнение локальной функции не забирает много времени, связанного с «захватом» (ссылками) переменных из охватывающей области, можно объявить ее как static. Тогда компилятор устранит ссылку на все, что объявлено во вложенных функциях, кроме других статических локальных функций.

Изменения с момента выпуска Первого Предварительного Просмотра

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

Обнуляемые ссылочные типы

Microsoft добавили больше опций для управления пустыми предупреждениями как в источнике (через #nullable и #pragma warning директивы), так и на уровне проекта. Также была изменена подписка на файл проекта к <NullableContextOptions> enable </NullableContextOptions>.

Асинхронные потоки

Microsoft изменила форму IAsyncEnumerable<T> интерфейса, которую ожидает компилятор. Это приводит к тому, что компилятор не синхронизируется с интерфейсом, предусмотренным в .NET Core 3.0 Preview 1, что может привести к некоторым проблемам. Однако планируется выпуск .NET Core 3.0 Preview 2, который вернет синхронизацию интерфейсов.

Обратите внимание

Microsoft ждет Ваших отзывов! Попробуйте новые особенности.