Posted on 6. June 2024

Рефакторинг коду з використанням псевдонімів будь-якого типу

Рефакторинг коду з використанням псевдонімів будь-якого типу

Ця стаття є третьою в серії з чотирьох частин, в яких ми розглянемо різні можливості C# 12. У цій частині ми зануримося у функцію «псевдонім будь-якого типу», яка дозволяє створювати псевдонім для будь-якого типу за допомогою директиви using. Ця серія статей дійсно починає набувати гарних рис:


  1. Рефакторинг вашого C# коду за допомогою первинних конструкторів

  2. Рефакторинг вашого C# коду за допомогою виразів колекцій

  3. Рефакторинг C# коду за допомогою псевдонімів для будь-яких типів (ця стаття)

  4. Рефакторинг вашого C# коду для використання лямбда-параметрів за замовчуванням

 

Всі ці можливості продовжують наш шлях до того, щоб зробити наш код більш читабельним і зручним для супроводу, і вважаються «повсякденними можливостями C#», які повинні знати розробники. Давайте зануримося!

Псевдонім будь-якого типу *️⃣

У C# 12 з’явилася можливість псевдонімізувати будь-який тип за допомогою директиви using. Ця функція дозволяє вказувати псевдоніми, які відображаються на інші типи. Це стосується типів кортежів, вказівників, масивів і навіть не відкритих узагальнених типів, і всі вони будуть доступні у вашому коді. Ця можливість є особливо корисною:


  • При роботі з довгими або складними іменами типів.

  • Коли вам потрібно розмежувати типи і вирішити потенційні конфлікти імен.

  • При визначенні типів кортежів значень, які ви маєте намір спільно використовувати у збірці.

  • Коли ви хочете додати ясності до вашого коду, використовуючи більш описові імена.

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

Типи посилань з нульовим значенням

_________________________________________________________________________

 

Ця функція підтримує більшість типів, за єдиним винятком типів посилань, які можна обнулити. Тобто, ви не можете створити псевдонім для nullable reference type, і компілятор C# повідомить про помилку CS9132: Using alias cannot be a nullable reference type. Наступні біти були вилучені зі специфікації функції, щоб допомогти прояснити цей момент:

Зразок додатку: Спостереження НЛО 🛸 

Демонстраційний додаток доступний на GitHub за адресою IEvangelist/alias-any-type. Це простий консольний додаток, який імітує спостереження за непізнаними літаючими об’єктами (НЛО). Якщо ви хочете приєднатися до нього локально, ви можете зробити це будь-яким з наведених нижче способів у робочій директорії на ваш вибір:

За допомогою Git CLI:

git clone https://github.com/IEvangelist/alias-any-type.git


За допомогою GitHub CLI:

gh repo clone IEvangelist/alias-any-type

Завантажте zip-файл:


Якщо ви бажаєте завантажити вихідний код, zip-файл доступний за наступною URL-адресою:

Щоб запустити програму, з кореневого каталогу виконайте наступну команду .NET CLI:

dotnet run —project ./src/Alias.AnyType.csproj


Коли програма запускається, вона друкує вступ до консолі і чекає на введення даних користувачем, перш ніж продовжити роботу.


Після натискання будь-якої клавіші, наприклад, клавіші Enter, програма випадковим чином генерує дійсні координати (широту і довготу), а потім, використовуючи ці координати, отримує метадані геокоду, пов’язані з координатами. Координати представлені у форматі градусів-хвилин-секунд (включаючи кардинальність). Під час роботи програми обчислюються відстані між згенерованими координатами, які подаються як спостереження НЛО.



Щоб зупинити програму, натисніть клавіші Ctrl + C.


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

Покрокове ознайомлення з кодом 👀

 

Ми використаємо цей розділ, щоб разом пройтися по коду. Є кілька цікавих аспектів коду, які я хотів би висвітлити, включаючи файл проекту, GlobalUsings.cs, деякі розширення та файл Program.cs. З наявного коду є кілька речей, які ми не будемо розглядати, наприклад, моделі реагування та деякі утилітарні методи.


Почнемо з розгляду файлу проекту:
Перше, на що слід звернути увагу, це те, що властивість ImplicitUsings має значення enable. Ця властивість з’явилася у C# 10 і дозволяє цільовому SDK (у цьому випадку Microsoft.NET.Sdk) неявно включати набір просторів імен за замовчуванням. Різні SDK включають різні простори імен за замовчуванням, для отримання додаткової інформації див. документацію про неявне використання директив.

Директиви неявного використання 📜


Елемент ImplicitUsing є особливістю MS Build, тоді як ключове слово global є особливістю мови C#. Оскільки ми обрали глобальне використання, ми також можемо скористатися цією можливістю, додавши наші власні директиви. Одним із способів додавання цих директив є додавання елементів Using у групу ItemGroup. Деякі директиви використання додаються з атрибутом Static, встановленим у true, що означає, що всі їхні static члени доступні без обмежень – докладніше про це пізніше. Атрибут Alias використовується для створення псевдоніма для типу, у цьому прикладі ми вказали псевдонім AsyncCancelable для типу System.Runtime.CompilerServices.EnumeratorCancellationAttribute. У нашому коді тепер ми можемо використовувати AsyncCancelable як псевдонім типу для атрибуту EnumeratorCancellation. Інші елементи Using створюють нестатичні та неалієсовані директиви global using для відповідних просторів імен.

 

Закономірність, що з’являється 🧩 

 

Ми починаємо спостерігати, як у сучасних кодових базах .NET з’являється загальний шаблон, коли розробники визначають файл GlobalUsings.cs для інкапсуляції всіх (або більшості) директив використання в одному файлі. Ця демонстраційна програма слідує цьому шаблону, давайте подивимось на файл далі:


Все у цьому файлі є директивою global using, що робить типи псевдонімів, статичні члени або простори імен доступними у всьому проекті. Перші три директиви призначені для загальних просторів імен, які використовуються у багатьох місцях програми. Наступна директива – це директива global using static для простору імен System.Math, яка робить всі статичні члени Math доступними без обмежень. Решта директив – це директиви global using, які створюють псевдоніми для різних типів, включаючи кілька кортежів, потік координат і CancellationTokenSource, на яке тепер можна просто посилатися через Signal.


Варто врахувати, що коли ви визначаєте тип псевдоніма кортежу, ви можете легко перейти до типу record, якщо вам потрібно додати поведінку або додаткові властивості. Наприклад, пізніше ви можете вирішити, що дійсно хочете додати деяку функціональність до типу Coordinates, ви можете легко змінити його на тип record:


 

Коли ви визначаєте псевдонім, ви фактично створюєте не тип, а ім’я, яке посилається на існуючий тип. У випадку визначення кортежів, ви визначаєте форму кортежу значень. Коли ви називаєте тип масиву псевдонімом, ви не створюєте новий тип масиву, а скоріше називаєте його псевдонімом, можливо, з більш описовою назвою. Наприклад, коли я визначаю API, який повертає IAsyncEnumerable, це дуже багато для написання. Натомість тепер я можу посилатися на тип, що повертається, як CoordinateStream у своїй кодовій базі.

Посилання на псевдоніми 📚

 

Було визначено декілька псевдонімів, деякі у файлі проекту, а інші у файлі GlobalUsings.cs. Давайте подивимося, як ці псевдоніми використовуються у коді. Почнемо з розгляду файлу верхнього рівня Program.cs:

 

У попередньому фрагменті коду показано, як псевдонім Signal використовується для створення екземпляру CancellationTokenSource. Як ви знаєте, клас CancellationTokenSource є реалізацією IDisposable, тому ми можемо використовувати інструкцію using, щоб гарантувати, що екземпляр Signal буде належним чином утилізовано, коли він вийде за межі області видимості. Ваша IDE розуміє ці псевдоніми, і коли ви наведете на них курсор, ви побачите фактичний тип, який вони представляють. Погляньте на наступний знімок екрана:


Вступ записується на консоль з виклику WriteIntroduction, безпосередньо перед входом в try / catch. Блок try містить цикл await foreach, який перебирає IAsyncEnumerable. Метод GetCoordinateStreamAsync визначено в окремому файлі. Я частіше використовую функціональність partial class, коли пишу програми верхнього рівня, оскільки це допомагає ізолювати проблеми. Уся функціональність, що базується на HTTP, визначена у файлі Program.Http.cs, давайте зосередимося на методі GetCoordinateStreamAsync:


 

Ви помітите, що він повертає псевдонім CoordinateStream, який є IAsyncEnumerable. Він приймає атрибут AsyncCancelable, який є псевдонімом для типу EnumeratorCancellationAttribute. Цей атрибут використовується для оформлення маркера скасування таким чином, щоб він використовувався разом з IAsyncEnumerable для підтримки скасування. Поки скасування не запитується, метод генерує випадкові координати, отримує метадані геокоду і повертає новий екземпляр CoordinateGeoCodePair. Метод GetGeocodeAsync запитує метадані геокоду для заданих координат, і у разі успіху повертає модель відповіді GeoCode. Наприклад, Microsoft Campus має такі координати:

Щоб переглянути JSON, відкрийте це посилання у вашому браузері. Тип CoordinateGeoCodePair не є псевдонімом, але це readonly record struct, яка містить Coordinates та GeoCode:

Повертаючись до класу Program, коли ми повторюємо кожну пару координати-геокод, ми деконструюємо кортеж на екземпляри типу Coordinates і GeoCode. Тип Coordinates є псевдонімом для кортежу з двох double значень, що представляють широту і довготу. Знову ж таки, наведіть курсор на цей тип у вашому IDE, щоб швидко побачити тип, подивіться на наступний знімок екрана:


Тип GeoCode  - це модель відповіді, яка містить інформацію про метадані геокоду. Потім ми використовуємо метод розширення, щоб перетворити Coordinates в кардиналізований рядок, який є рядковим представленням координат у форматі градусів-хвилин-секунд. Особисто мені подобається, як легко використовувати псевдоніми у вашій кодовій базі. Давайте розглянемо деякі методи розширення, які розширюють або повертають псевдонімні типи:

 

 

Цей метод розширення розширює тип псевдоніма Coordinates і повертає рядкове представлення координат. Він використовує метод розширення ToDMS для перетворення широти і довготи у формат градусів-хвилин-секунд. Метод розширення ToDMS визначається наступним чином:

Якщо ви пам’ятаєте, псевдонім DMS – це кортеж з трьох значень, що представляють градуси, хвилини та секунди. Метод розширення ToDMS приймає значення double і повертає кортеж DMS. Метод розширення ToCardinal використовується для визначення кардинального напрямку координат і повертає значення N, S, E або W. Методи Abs, Sign і Floor є статичними членами простору імен System.Math, псевдонім якого міститься у файлі GlobalUsings.cs.


Крім того, програма відображає деталі спостереження НЛО на консолі, включаючи координати, метадані геокоду та відстань, пройдену між спостереженнями. Це відбувається постійно, доки користувач не зупинить програму комбінацією клавіш Ctrl + C.

Наступні кроки 🚀

Не забудьте спробувати це у своєму власному коді! Поверніться до останньої частини цієї серії статей, де ми розглянемо параметри лямбда за замовчуванням. Щоб продовжити дізнаватися більше про цю функцію, відвідайте наступні ресурси: