Использование Roslyn в качестве скриптового языка в своем приложении

Платформа Roslyn позволяет разработчикам встраивать компиляцию и исполнения кода прямо в приложение. Эта часть платформы Roslyn называется Scripting API. С её помощью вы можете исполнить код, который вводит пользователь или который, например, хранится в базе данных.

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

Для того, чтобы работать с компонентами Roslyn следует добавить ссылку на сборки Roslyn.

Для использования скриптового механизма Roslyn нам понадобится объект ScriptingEngine. Этот объект отвечает за исполнение кода и имеет API для управления зависимостями на внешние модули:

  • метод ImportNamespace – позволяет импортировать пространство имен для использования его в коде (аналог using);
  • метод AddReference – позволяет задать ссылку на внешюю сборку;
  • метод SetReferenceSearchPaths – позволяет указать одну или несколько папок, в которых будет производится поиск сборок для разрешения зависимостей на внешние сборки.

Кроме задания зависимостей, можно определить какие зависимости уже установлены. Для этого существуют методы GetReferences() и GetImportedNamespaces().

После создания ScriptingEngine и указания всех зависимостей нужно создать сессию, в рамках которой мы будем исполнять код. Сессия позволяет объединить последовательность нескольких вызовов и разделять между собой какие-то данные. В некотором приближении сессию можно рассматривать как запуск приложения. Для создания новой сессии используется метод CreateSession.

После того, как сессия создана, можно запускать собственный код. Для этого у объекта Session (результат выполнения CreateSession) существует метод Execute. Я создал консольное приложение, в котором считываю пользовательский ввод и запускаю его:

ScriptEngine host = new ScriptEngine();
host.ImportNamespace("System");

Session session = host.CreateSession();

Console.WriteLine("Define your command:");
string input;
while ((input = Console.ReadLine()) != String.Empty)
{
	try
	{
		session.Execute(input);
	}
	catch (CompilationErrorException ex)
	{
		Console.WriteLine(ex.Message);
	}
}

Результат работы приложения:

Метод Execute возвращает результат в виде общего типа object. Это полезно тогда, когда исполняемое выражение возвращает какой-то результат, который можно использовать далее:

try
{
	var result = session.Execute(input);
	Console.WriteLine(result);
}
catch (CompilationErrorException ex)
{
	Console.WriteLine(ex.Message);
}

В результате:

Если предполагается в качестве результата получить какой-то конкретный тип, то использовать object не очень удобно. В этом случае можно использовать перегруженный метод Execute<T>():

try
{
	int result = session.Execute<int>(input);
	Console.WriteLine(result);
}
catch (CompilationErrorException ex)
{
	Console.WriteLine(ex.Message);
}

В целом, Roslyn Scripting API мне представляется очень преспективной разработкой. С его помощью можно настраивать отдельные части приложения с одной стороны гибко, а с другой — без необходимости перекомпиляции всего приложения. Если вдуматься, то в некотором смысле Roslyn Scripting API является конкуретном Workflow Foundation, которая так и не заняла место в сердцах разработчиков.