Иногда в процессе описания бизнес-логики, необходимо составить граф асинхронных операций с внутренними зависимостями, т.е. когда задачи выполняются асинхронно, но некоторые задачи зависят от других и тем самым вынуждены «ждать» пока из можно будет запустить. В этом посте я хочу показать как эту проблему можно решить путем создания графической DSL, которая позволит разработчику визуально определить граф зависимостей.
N.b.: статья на английском и исходный код находятся тут
Вообще говоря, предметно-ориентированные языки (domain-specific languages, DSL для краткости) бывают трех видов. Первая разновидность – текстовый DSL, который определен исключительно через текст и структуру, и связан с конкретным процессом преобразования этого текста в код. Вторая разновидность – структурный DSL, где содержание определяется с помощью древовидного или графоподобного редактора. Я же хочу обсудить третий тип – это графический DSL, где разработчик работает с графическим редактором для создания визуальной структуры, которую в последствии можно превратить в код.
В этой статье мы создадим простой графический DSL, что позволяет конечному пользователю определить асинхронные операций, которые будут организованы с помощью механизма Pulse & Wait. Чтобы собрать прилагаемый пример, вам потребуется Visual Studio 2008 с Visual Studio SDK. Мы будем использовать инструменты Microsoft DSL Tools (входят в состав SDK) для создания наших DSL.
Поскольку работать с Pulse & Wait трудно, я хочу сделать графический DSL, который позволил бы мне определить последовательность операций, которые могут быть организованы с помощью механизма Pulse & Wait. В частности, я хочу иметь возможность перетаскивания асинхронных блоков в редакторе, а также возможность определять связи между ними для формирования правил асинхронного, зависимого исполнения.
Прежде чем мы начнем, позвольте мне изложить наиболее важные моменты при работе с DSL Tools:
Чтобы создать DSL-проект в Visual Studio, выберите New Project, а потом Other Project Types → Extensibility → Domain-Specific Language Designer.
После нажатия кнопки OK, вам будет показан визард, где вы сможете определить некоторые особенности DSL которую вы создаете.
Когда закончите работу с мастером, вы получите каркас определения DSL. Если вы никогда не работали с DSL-функционалом в Visual Studio, то скриншот может вас немного шокировать.
В процессе редактирования DSL принимают участие следующие элементы:
Давайте теперь углубимся в процесс создания нашей DSL.
Как я уже писал, тулбокс содержит все элементы, с которыми вам предстоит работать. Эти элементы разделены на две группы – «логическую» и «визуальную». Логические элементы – это те, которые определяют структуру (т.е. домен) в вашем DSL. Визуальные элементы отражают те прямоугольники, линии, и пободные элементы, которыми пользователь оперирует при работе с DSL.
Центральная концепция логической структуры DSL – это класс домена (domain class). Этот класс может представлять все что угодно, в зависимости от того, с какой предметной областью вы работаете. Поскольку мы работаем с асинхронными операциями, один из наших доменных классов будет называться
У доменного класса могут быть свойства, т.е. значения, которые пользователь может устанавливать. У нашего класса
Тут небольшая проблемка – на самом деле пользователь не перетаскивает к себе в модель доменный класс напрямую. Вместо этого, он перетаскивает себе в модель
Определив доменный класс
Прежде чем мы начнем работать с созданием тулбокс-контролов для нашей DSL (а это весело), нужно поговорить об отношениях (relationships) между элементами. Есть два типа отношений – Embedding Relationship и Reference Relationship. Если вы используете embedding relationship, элемент А будет полностью заключен в состав элемента В. Например, если у меня есть swimlane (большой горизонтальный кусок визуального пространства), и в него нужно вставлять целые классы, то имеет смысл использовать embedding relationship. Если же у меня просто есть блоки к которым нужно прицеплять комментарии, пойдет и reference relationship.
Давайте посмотрим на то, как мы будет использовать элементы для нашей конкретной задачи. В «корне» нашей можели у нас фигурирует элемент
Оранжевые боксы символизируют отношения, с названиями и кардинальностью отношений по обе стороны. Кардинальность позже регулируется DSL-дизайнером, так что конечный пользователь не сможет ее нарушить. Что же касается отношений, то смысл этих оранжевых боксов в том, что они позволяют связывать вместе разные доменные классы при редактировении уже готового DSL.
Внимание: дизайнер DSL применяет к вашему языке ряд правил, одно из которых требует, что все элементы являются частью чего-то. Это означает, что все элементы должны сводиться к одному, «конревому» контейнеру. (Если вспомнить, что DSL == XML, причина этого требования должна быть очевидна.)
Мы воспользовались embedding relationship для того чтобы сказать нашей DSL, что и процессы и комментарии являются частью общей модели. Теперь мы можем использовать reference relationship чтобы определить, что у процессов могут быть комментарии, и что эти два элемента можно связывать.
Пунктирная линия выше означает reference relationship, т.е. в нашем случае, операция может просто ссылаться на комментарий – а не содержать его. Конечно, такое отношение имеет свой визуальный элемент (линию, которая связывает операцию и комментарий), о чем мы сейчас и поговорим.
Получив логическую и визуальную часть вашего DSL, нужно дать пользователям возможность перетаскивать элементы из этой DSL на их дизайнер. Вот с чего нужно начинать – с узла Editor в DSL Explorer:
Для того, чтобы создать новый элемент для тулбокса, нажмите правой кнопкой по всей DSL. У вас появится следующее меню:
Тут две опции – коннекторы и элементы. Коннекторы – это линии (даже возможно со стрелочками) которые соединяют вместе элементы. А элементы – это блокообразные структуры.
После создания нового элемента, нажмите F4 и вы увидите свойства этого элемента:
Что тут важно, так это то, что несколько из этих свойств нужно обязательно заполнить – иначе DSL не запустится. Из тех что очевидно нужно определить – определение доменного класса, который отражает элемент, а также определение иконки. (Пара дефолтных иконок уже предоставлена, так что если лень создавать свои, можно воспользоваться готовыми.)
Прорезюмируем процесс создания DSL:
Наш DSL готов наполовину: мы определили только визуальную часть. После того, как мы трансформировали все шаблоны и запустили наш язык, мы наконец-то можем начать играть с нашей DSL:
Для нашей асинхронной DSL, мы определили следующие идиомы:
Давайте рассмотрим реальный пример: процесс поедания завтрака (знаю, не очень-то умно). Чтобы приготовить завтрак, нужно поставить чайник, а также положить хлеб в тостер – в любом порядке. Пока все готовится, я хочу достать варенье, но только если я уже включил тостер. Когда я получил и готовый хлеб и достал варенье, я могу сделать бутерброд. И только тогда, когда готовы и будерброд и чай, я могу приступать к поглощению завтрака.
Воспользовавшись нашей DSL, весь процесс можно определить вот так:
Как вы уже наверное догадались, жирные линии символизируют finish-to-start, а пунктирная – start-to-start.
Визуальная модель завтрака существует только как DSL, поэтому нам нужен Т4 чтобы превратить ее в полноценный код. К счастью, к тому времени как мы должны делать конверсию, модель уже переконвертирована в формат XML, и остается только обойти ее и сгенерировать то, что нужно.
Производство конечного результата в Т4 движится несколькими методами, такими как
Я не буду тут представлять код трансформации Т4 – его можно скачать по ссылке выше. Вместо этого, я покажу что произведет наша DSL из определения завтрака.
Немало кода! Но зато этот код отражает ту структуру, которую мы определили. Теперь остается только воспользоваться сгенерированной структурой:
Результат вызова этого кода примерно такой:
Хотя конечно же
DSL Tools – это сложный, но мощный инструментарий. Ключевая характеристика этого пакета – простота работы с языком после того, как он был определен. Тут я смог только поверхностно описать работу с DSL Tools, т.к. возможностей и ньюансов очень много. Надеюсь что это пост мотивирует кого-то на проведение собственных исследований. ■
N.b.: статья на английском и исходный код находятся тут
Введение
Вообще говоря, предметно-ориентированные языки (domain-specific languages, DSL для краткости) бывают трех видов. Первая разновидность – текстовый DSL, который определен исключительно через текст и структуру, и связан с конкретным процессом преобразования этого текста в код. Вторая разновидность – структурный DSL, где содержание определяется с помощью древовидного или графоподобного редактора. Я же хочу обсудить третий тип – это графический DSL, где разработчик работает с графическим редактором для создания визуальной структуры, которую в последствии можно превратить в код.
В этой статье мы создадим простой графический DSL, что позволяет конечному пользователю определить асинхронные операций, которые будут организованы с помощью механизма Pulse & Wait. Чтобы собрать прилагаемый пример, вам потребуется Visual Studio 2008 с Visual Studio SDK. Мы будем использовать инструменты Microsoft DSL Tools (входят в состав SDK) для создания наших DSL.
Описание проблемы
Поскольку работать с Pulse & Wait трудно, я хочу сделать графический DSL, который позволил бы мне определить последовательность операций, которые могут быть организованы с помощью механизма Pulse & Wait. В частности, я хочу иметь возможность перетаскивания асинхронных блоков в редакторе, а также возможность определять связи между ними для формирования правил асинхронного, зависимого исполнения.
Создание DSL
Прежде чем мы начнем, позвольте мне изложить наиболее важные моменты при работе с DSL Tools:
- В DSL Tools, графические DSL сами сделаны с помощью графического DSL. На первый взгляд это может показаться запутанным, но, в принципе, вы должны понимать, что большинство наших асинхронных DSL (которую я называю здесь AsyncDsl) будут разработаны с использованием визуальных элементов – не с языком программирования. Конечно, за кулисами будет много кода, но мы не будем с ним часто сталкиваться.
- DSL инструменты широко используют технологию T4. Наша графическая DSL в действительности является только визуальным представлением XML, и Т4 превращает этот XML в код. Таким образом, когда вы редактируете визуальные элементы с DSL Tools, вы действительно редактируете XML.
- Ваш DSL по-прежнему создается с использованием C#, и он компилируются. Вы можете расширить его с помощью частичных (partial) классов и т.п., что позволит вашему DSL вести себя определенным образом. Мы не будем делать ничего подобного в данной статье.
- Cоздание DSL с помощью инструментов DSL Tools касается только визуальной части – той части, которая позволяет пользователю визуально создавать XML модели. Та часть, которая превращает ее в код простого текста – отдельная проблемы, с которой мы встретимся позже.
Чтобы создать DSL-проект в Visual Studio, выберите New Project, а потом Other Project Types → Extensibility → Domain-Specific Language Designer.
После нажатия кнопки OK, вам будет показан визард, где вы сможете определить некоторые особенности DSL которую вы создаете.
- На первой странице, кроме определения имени вашего языка, вы также можете выбрать начальный шаблон. Этот шаблон определяет, какими начальными возможностями обладает DSL имеет – например, выбирая Task Flow, вы определяете, что изначальные элементы DSL будут относиться к схемо-подобным (flowchart) структурам. Независимо от того, какой шаблон вы выберете на данном этапе, вы всега сможете переопределить поведение вашей DSL путем удаления первочино сгенерированных элементов.
- Вторая страница позволяет вам выбрать файловое расширение (extension) для вашего DSL. Это расширение будет фигурировать в тех местах, где вы будете вставлять вашу DSL в ваши собственные проекта. Помимо расширения, визард генерирует иконку.
- Третья страница позволяет задать некоторые строки, которые определяют ваше DSL, таких как имя продукта которому ваш DSL принадлежит.
- Четвертая страница фактически заставляет вас подписать вашу сборку – либо уже существующим, либо новым ключем.
Когда закончите работу с мастером, вы получите каркас определения DSL. Если вы никогда не работали с DSL-функционалом в Visual Studio, то скриншот может вас немного шокировать.
В процессе редактирования DSL принимают участие следующие элементы:
- Dsl Designer Toolbox. Эта панель содержит все элементы, с которыми вы будете работать при проектировании ваших DSL. Используются эти элементы так же как например в WinForms – берете элемент и перетаскиваете его в окно редактора (то центральное окно со странными боксами).
- Сам DSL-дизайнер. Вообще-то это файл с расширением
.dsl
, но как видите, редактор мегавизуальный – как я уже сказал, DSLи сами по себе строятся с помощью других DSL. У этого DSL две части – с левой стороны находятся классы и отношения между ними, а в правой – визуальные элементы, т.е. визуальные отражения DSL-концептов, с которыми будет работать конечный пользователь. Тем самым, можно представить себе DSL так: справа находится визуализация, слева – логика.
- Solution Explorer. При создании DSL, вы получите два проекта – один, определяющий DSL который вы делаете, и другой, который определяет редактор компонентов, связанных с DSL. Мы еще поговорим об этом позже – сейчас важно лишь отметить одну единственную кнопку, которая трансформирует все шаблоны:
Это очень важная кнопка. Как я уже говорил, DSLи – это XML-спецификации, которые трансформируются в код. Это означает, что для того чтобы обновить определение нашего DSL (а определение – это тоже DSL), нужно трансформировать все шаблоны в C#. Кнопка выше именно это и делает. Поэтому если у вас вдруг в конечной DSL не отражаются какие-то изменения, которые вы уверены что сделали – значит вы забыли нажать на эту кнопку.
- The DSL explorer. Это совершенно новая панель в студии, которыя представляет ваш DSL в форме дерева, и выглядит примерно так:
Это дерево инкапсулирует многие структурные аспекты DSL. Важно отметить, что некоторые узлы дерева имеют свой набор свойств, которые можно увидеть, нажав F4.
- Страницы для редактированя свойств (property pages) существуют как для элементов дерева DSL Explorer, так и для визуальных элементов в редакторе DSL. Некоторые элементы DSL можно редактировать прямо «в редакторе» – например, вы можете определить форму отношений (one-to-many, many-to-many, и т.д.) между двумя элементами, не открывая при этом панель свойств. Как это удобней делать – решать вам.
Давайте теперь углубимся в процесс создания нашей DSL.
Арранжировка визуальных элементов
Как я уже писал, тулбокс содержит все элементы, с которыми вам предстоит работать. Эти элементы разделены на две группы – «логическую» и «визуальную». Логические элементы – это те, которые определяют структуру (т.е. домен) в вашем DSL. Визуальные элементы отражают те прямоугольники, линии, и пободные элементы, которыми пользователь оперирует при работе с DSL.
Центральная концепция логической структуры DSL – это класс домена (domain class). Этот класс может представлять все что угодно, в зависимости от того, с какой предметной областью вы работаете. Поскольку мы работаем с асинхронными операциями, один из наших доменных классов будет называться
Operation
:У доменного класса могут быть свойства, т.е. значения, которые пользователь может устанавливать. У нашего класса
Operation
есть свойства Timeout
, Name
и Description
которые конечный пользователь сможет определить после того, как перетащит инстанс объекта Operation
к себе в модель.Тут небольшая проблемка – на самом деле пользователь не перетаскивает к себе в модель доменный класс напрямую. Вместо этого, он перетаскивает себе в модель
OperationShape
, который является визуальным отражением Operation
. Этот класс формируется из GeometryShape
(взято из того же тулбокса):Определив доменный класс
Operation
а также его визуальное представление OperationShape
, их нужно связать вместе (если запустить как есть, ничего работать не будет). Для этого используется элемент Diagram Element Map. Фактически, эта штука – линия, которая соединяет два элемента, определяя ассоциацию между ними. Но даже если ее добавить, все равно пока ничего работать не будет.Отношения между элементами
Прежде чем мы начнем работать с созданием тулбокс-контролов для нашей DSL (а это весело), нужно поговорить об отношениях (relationships) между элементами. Есть два типа отношений – Embedding Relationship и Reference Relationship. Если вы используете embedding relationship, элемент А будет полностью заключен в состав элемента В. Например, если у меня есть swimlane (большой горизонтальный кусок визуального пространства), и в него нужно вставлять целые классы, то имеет смысл использовать embedding relationship. Если же у меня просто есть блоки к которым нужно прицеплять комментарии, пойдет и reference relationship.
Давайте посмотрим на то, как мы будет использовать элементы для нашей конкретной задачи. В «корне» нашей можели у нас фигурирует элемент
ExampleModel
. Я даже имя этого элемента менять не буду, т.к. он не будет фигурировать в конечной DSL. Для того, чтобы определить то, что моя модель содержит процессы и комментарии, я рисую линии embedding relationship между соответствующими классами и получаю следующую картину:Оранжевые боксы символизируют отношения, с названиями и кардинальностью отношений по обе стороны. Кардинальность позже регулируется DSL-дизайнером, так что конечный пользователь не сможет ее нарушить. Что же касается отношений, то смысл этих оранжевых боксов в том, что они позволяют связывать вместе разные доменные классы при редактировении уже готового DSL.
Внимание: дизайнер DSL применяет к вашему языке ряд правил, одно из которых требует, что все элементы являются частью чего-то. Это означает, что все элементы должны сводиться к одному, «конревому» контейнеру. (Если вспомнить, что DSL == XML, причина этого требования должна быть очевидна.)
Мы воспользовались embedding relationship для того чтобы сказать нашей DSL, что и процессы и комментарии являются частью общей модели. Теперь мы можем использовать reference relationship чтобы определить, что у процессов могут быть комментарии, и что эти два элемента можно связывать.
Пунктирная линия выше означает reference relationship, т.е. в нашем случае, операция может просто ссылаться на комментарий – а не содержать его. Конечно, такое отношение имеет свой визуальный элемент (линию, которая связывает операцию и комментарий), о чем мы сейчас и поговорим.
Тулбоксы, наконец-то
Получив логическую и визуальную часть вашего DSL, нужно дать пользователям возможность перетаскивать элементы из этой DSL на их дизайнер. Вот с чего нужно начинать – с узла Editor в DSL Explorer:
Для того, чтобы создать новый элемент для тулбокса, нажмите правой кнопкой по всей DSL. У вас появится следующее меню:
Тут две опции – коннекторы и элементы. Коннекторы – это линии (даже возможно со стрелочками) которые соединяют вместе элементы. А элементы – это блокообразные структуры.
После создания нового элемента, нажмите F4 и вы увидите свойства этого элемента:
Что тут важно, так это то, что несколько из этих свойств нужно обязательно заполнить – иначе DSL не запустится. Из тех что очевидно нужно определить – определение доменного класса, который отражает элемент, а также определение иконки. (Пара дефолтных иконок уже предоставлена, так что если лень создавать свои, можно воспользоваться готовыми.)
Запускаем!
Прорезюмируем процесс создания DSL:
- Сделали базовую DSL с использованием визарда
- Добавили доменные классы представляющие нужные нам концепции, такие как процесc.
- Добавили отношения между дуоменными классами – в нашем случае, определили тот факт, что операции пренадлежат общей модели, и что у них есть комментарии. Также добавили операции перехода между операциями, а также элементы начала и конца.
- Определили визуальные элементы, которые будет использовать наша DSL.
- Связали визуальные элементы с доменными классами.
- Создали тулбокс-контролы и связали их с соответствующими классами.
Наш DSL готов наполовину: мы определили только визуальную часть. После того, как мы трансформировали все шаблоны и запустили наш язык, мы наконец-то можем начать играть с нашей DSL:
Концепции
Для нашей асинхронной DSL, мы определили следующие идиомы:
- Operation
Это наш unit of work, например ‘приготовить чай’. Мы подразумеваем, что операция может пройти без каких-либо сбоев.
- Process
Процесс – это последовательность операций в графе. Единственная причина по которой мы добавили это элемент – это для того, чтобы иметь возможность держать несколько графов операций в одном классе.
- Start и Finish
Процесс должен где-то начинаться и заканчиваться, поэтому мы создали два элемента чтобы маркировать состояния начала и конца.
- Finish-to-start transition
Этот переход определяет, что операция может быть запущена только после того, как другая операция завершена.
- Start-to-start transition
Этот переход определяет что операция может начаться только тогда, когда другая операция была запущена, и не раньше.
Давайте рассмотрим реальный пример: процесс поедания завтрака (знаю, не очень-то умно). Чтобы приготовить завтрак, нужно поставить чайник, а также положить хлеб в тостер – в любом порядке. Пока все готовится, я хочу достать варенье, но только если я уже включил тостер. Когда я получил и готовый хлеб и достал варенье, я могу сделать бутерброд. И только тогда, когда готовы и будерброд и чай, я могу приступать к поглощению завтрака.
Воспользовавшись нашей DSL, весь процесс можно определить вот так:
Как вы уже наверное догадались, жирные линии символизируют finish-to-start, а пунктирная – start-to-start.
Трансформируем модель с помощью Т4
Визуальная модель завтрака существует только как DSL, поэтому нам нужен Т4 чтобы превратить ее в полноценный код. К счастью, к тому времени как мы должны делать конверсию, модель уже переконвертирована в формат XML, и остается только обойти ее и сгенерировать то, что нужно.
Производство конечного результата в Т4 движится несколькими методами, такими как
WriteLine()
(пишет строку в кончный файл) и Push/PopIndent()
(держат в стеке кол-во отступов).Я не буду тут представлять код трансформации Т4 – его можно скачать по ссылке выше. Вместо этого, я покажу что произведет наша DSL из определения завтрака.
namespace Debugging<br/>
{<br/>
using System.Threading;<br/>
partial class Breakfast<br/>
{<br/>
private readonly object MakeSandwichLock = new object();<br/>
private readonly object EatBreakfastLock = new object();<br/>
private readonly object GetJamLock = new object();<br/>
private bool MakeTeaIsDone;<br/>
private bool ToastBreadIsDone;<br/>
private bool GetJamIsDone;<br/>
private bool MakeSandwichIsDone;<br/>
private bool MakeTeaStarted;<br/>
private bool ToastBreadStarted;<br/>
private bool GetJamStarted;<br/>
private bool MakeSandwichStarted;<br/>
protected internal void MakeTea()<br/>
{<br/>
MakeTeaImpl();<br/>
lock(EatBreakfastLock)<br/>
{<br/>
MakeTeaIsDone = true;<br/>
Monitor.PulseAll(EatBreakfastLock);<br/>
}<br/>
}<br/>
protected internal void ToastBread()<br/>
{<br/>
lock(GetJamLock)<br/>
{<br/>
ToastBreadIsDone = true;<br/>
Monitor.PulseAll(GetJamLock);<br/>
}<br/>
ToastBreadImpl();<br/>
lock(MakeSandwichLock)<br/>
{<br/>
ToastBreadIsDone = true;<br/>
Monitor.PulseAll(MakeSandwichLock);<br/>
}<br/>
}<br/>
protected internal void GetJam()<br/>
{<br/>
lock(GetJamLock)<br/>
if(!(ToastBreadStarted))<br/>
Monitor.Wait(GetJamLock);<br/>
GetJamImpl();<br/>
lock(MakeSandwichLock)<br/>
{<br/>
GetJamIsDone = true;<br/>
Monitor.PulseAll(MakeSandwichLock);<br/>
}<br/>
}<br/>
protected internal void MakeSandwich()<br/>
{<br/>
lock(MakeSandwichLock)<br/>
if(!(ToastBreadIsDone && GetJamIsDone))<br/>
Monitor.Wait(MakeSandwichLock);<br/>
MakeSandwichImpl();<br/>
lock(EatBreakfastLock)<br/>
{<br/>
MakeSandwichIsDone = true;<br/>
Monitor.PulseAll(EatBreakfastLock);<br/>
}<br/>
}<br/>
protected internal void EatBreakfast()<br/>
{<br/>
lock(EatBreakfastLock)<br/>
if(!(MakeTeaIsDone && MakeSandwichIsDone))<br/>
Monitor.Wait(EatBreakfastLock);<br/>
EatBreakfastImpl();<br/>
}<br/>
}<br/>
}<br/>
Немало кода! Но зато этот код отражает ту структуру, которую мы определили. Теперь остается только воспользоваться сгенерированной структурой:
namespace Debugging<br/>
{<br/>
partial class Breakfast<br/>
{<br/>
AutoResetEvent eatHandle = new AutoResetEvent(false);<br/>
Random rand = new Random();<br/>
public void Prepare()<br/>
{<br/>
ThreadStart[] ops = new ThreadStart[] {<br/>
MakeTea,<br/>
GetJam,<br/>
ToastBread,<br/>
MakeSandwich,<br/>
EatBreakfast };<br/>
foreach (ThreadStart op in ops)<br/>
op.BeginInvoke(null, null);<br/>
eatHandle.WaitOne();<br/>
}<br/>
private int RandomInterval<br/>
{<br/>
get<br/>
{<br/>
return (1 + rand.Next() % 10) * 100;<br/>
}<br/>
}<br/>
public void MakeTeaImpl()<br/>
{<br/>
Thread.Sleep(RandomInterval);<br/>
Console.WriteLine("Make tea");<br/>
}<br/>
public void ToastBreadImpl()<br/>
{<br/>
Thread.Sleep(RandomInterval);<br/>
Console.WriteLine("Toast bread");<br/>
}<br/>
public void GetJamImpl()<br/>
{<br/>
Thread.Sleep(RandomInterval);<br/>
Console.WriteLine("Get jam");<br/>
}<br/>
public void MakeSandwichImpl()<br/>
{<br/>
Thread.Sleep(RandomInterval);<br/>
Console.WriteLine("Make sandwich");<br/>
}<br/>
public void EatBreakfastImpl()<br/>
{<br/>
Thread.Sleep(RandomInterval);<br/>
Console.WriteLine("Eat breakfast");<br/>
eatHandle.Set();<br/>
}<br/>
}<br/>
}<br/>
Результат вызова этого кода примерно такой:
Make tea
Toast bread
Get jam
Make sandwich
Eat breakfast
All done
Хотя конечно же
Make tea
и Toast bread
могут фигурировать и в другом порядке.Заключение
DSL Tools – это сложный, но мощный инструментарий. Ключевая характеристика этого пакета – простота работы с языком после того, как он был определен. Тут я смог только поверхностно описать работу с DSL Tools, т.к. возможностей и ньюансов очень много. Надеюсь что это пост мотивирует кого-то на проведение собственных исследований. ■