Когда Не Можешь Дождаться Record's

Думаю, многие C# разработчики с нетерпением ждали появления первичных конструкторов и записей в C# 6.0 и были расстроены тем, что эта возможность была отложена до версии 7. Под конец рабочего четверга желание иметь неизменяемые типы во что бы то ни стало пересилило мое терпение и я решил написать утилиту, которая их генерирует. Если кому интересно, смотрите кат. Постановка проблемы была очень ясной; запись должна содержать:

  • Свойства с публичными геттерами
  • Конструктор с параметрами для инициализации всех свойств
  • Copy() с тем же набором параметров, но имеющим значение по умолчанию для каждого
  • Перегрузки Equals и GetHashCode, реализация IEquatable
  • Операторы == и !=
В целом всё так же, как и в кейс-классах в Scala. Для описания записей использовался несколько упрощенный синтаксис C#:
  
  
  
  
   

namespace Records { using System; record Test { Int32 Id; String Name; Nullable<Decimal> Amount; } }

Текст анализируется с помощью Nemerle.PEG, в результате получается следующая грамматика:

grammar { ANY = !['\u0000'.

'\u001F'] !'\u007F' ['\u0000'.

'\uFFFF']; ws : void = ("\r\n" / "\n" / "\r" / "\t" / ' ')*; letter = [Lu, Ll, Lt, Lm, Lo]; digit = ['0'.

'9']; keyword = "using" / "record" / "namespace"; identifier : string = letter (letter / digit)*; path : string = identifier (".

" identifier)*; genericTypeDefinition : string = identifier ws"<"ws (genericTypeDefinition / identifier)(ws","ws (genericTypeDefinition / identifier))* ws">"; property : PropertyDefinition = !keyword (genericTypeDefinition / identifier) ws identifier ws";"; properties : List[PropertyDefinition] = (ws property ws)+; import : ImportDefinition = "using" ws path";"; record : RecordDefinition = "record" ws identifier ws "{" ws property (ws property)* ws "}"; nmspace : NamespaceDefinition = "namespace" ws path ws "{" (ws import)* ws record (ws record)* ws "}" ws !ANY; }

На основе DOM, полученного в результате работы парсера, с помощью CodeDOM генерируется исходный код C#, который затем компилируется в сборку с помощью CSharpCodeProvider. Для простоты реализации было введено ограничение — каждый файл должен содержать новое пространство имен (в будущем планирую снять это ограничение).

В остальном язык оказался гибким: пространство имен можно сразу импортировать в другие файлы, объявленные типы можно сразу использовать как типы полей в других записях.

Позвольте мне привести вам простой пример использования.

Создадим файл Units.rcs следующего содержания:

namespace Units { using System; record Unit1 { Int32 Id; String Name; } record Unit2 { Int32 Id; Unit1 Unit; Decimal Amount; } }

а также Delivery.rsc

namespace Delivery { using System; using Units; record Address { String CityName; String Street; String House; } record Package { Address Destination; Unit2 Contents; } }

Для получения сборок необходимо выполнить следующую команду:

RecSharp -i Units.rcs Delivery.rcs -o Records.dll

Результатом будет сборка, которую можно будет подключить к проекту и использовать с объектами.

Вы также можете использовать расширение для VisualStudio, которое генерирует исходники типа T4. Проект можно посмотреть здесь: РекШарп (в Релизах есть бинарники для тех, кто не хочет устанавливать Nemerle) Расширение для VisualStudio: RecSharp.VisualStudio (опять же, релизы содержат собранный .

vsix) В будущем я, возможно, перейду с CodeDOM на Roslyn, но после первого беглого осмотра его API для генерации кода выглядит сложнее, чем CodeDOM. Буду рад, если утилита кому-то пригодится.

Теги: #C++ #Record #immutable #Ненормальное программирование #.

NET #C++

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.