Думаю, многие C# разработчики с нетерпением ждали появления первичных конструкторов и записей в C# 6.0 и были расстроены тем, что эта возможность была отложена до версии 7. Под конец рабочего четверга желание иметь неизменяемые типы во что бы то ни стало пересилило мое терпение и я решил написать утилиту, которая их генерирует. Если кому интересно, смотрите кат. Постановка проблемы была очень ясной; запись должна содержать:
- Свойства с публичными геттерами
- Конструктор с параметрами для инициализации всех свойств
- Copy() с тем же набором параметров, но имеющим значение по умолчанию для каждого
- Перегрузки Equals и GetHashCode, реализация IEquatable
- Операторы == и !=
Текст анализируется с помощью Nemerle.PEG, в результате получается следующая грамматика:namespace Records { using System; record Test { Int32 Id; String Name; Nullable<Decimal> Amount; } }
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++
-
Глобальная Война С Наличными Деньгами
19 Oct, 24 -
Обучение По Программе Masa В Израиле
19 Oct, 24