[.NET, C#] Скриптинг в C# или динамическое выполнение в runtime

Автор Сообщение
news_bot ®

Стаж: 6 лет 9 месяцев
Сообщений: 27286

Создавать темы news_bot ® написал(а)
20-Апр-2021 12:31

Привет Хабр!Думаю, не многие знают, что в C# есть штука на подобии eval из других языков. Благодаря Rosyln API, можно во время выполнения скомпилировать и выполнить C# код. Пример использования можете посмотреть в моей реализации REPL-а для C#.Впервые с такой штукой, как REPL, я познакомился когда теребил питона. В мире .NET есть похожая вещь под названием C# Interactive (CSI). Довольно удобная штука, но у нее есть один большой минус — она входит в состав инструментов Visual Studio, так что без установки VS, не получится ее использовать, а чтобы запускать ее без запуска VS, вообще надо лезть в ее недра (а точнее, через консоль запустить C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\Tools\VsDevCmd.bat), что так себе.Есть еще такие проекты, как dotnet-script и cs-script (они работают через Microsoft.CodeAnalysis.CSharp.Scripting), но у них есть фатальный недостаток — они написаны не мной. Вот и появилась мысль, написать свой корявый велосипед, но со своими фичами! (которые тоже коряво работают). После недолгих поисков, мой взор упал на сие чудо: Microsoft.CodeAnalysis.CSharp.Scripting. Из плюсов — удобный API, возможность выполнять код без классов и namespace-ов.Для начала, нужно поставить нугет пакет Microsoft.CodeAnalysis.CSharp.Scripting и сделать using
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;
CSharpScript - статичный класс, который поможет нам создать скрипт, включает 3 метода:
  • Create - создает Script с указанным кодом и параметрами, который можно будет в последствии скомпилировать и запустить
  • RunAsync - который компилирует, выполняет переданный код и возвращает ScriptState
  • EvaluateAsync - выполняет код и возвращает результат выполнения
CSharpScript.Create можно использовать, когда вам нужно предварительно скомпилировать скрипт и часто вызывать его.
var script = CSharpScript.Create("System.Console.WriteLine("Hello from script")");
script.Compile();
await script.RunAsync();
Eсли не вызвать Compile(), то код будет скомпилирован при первом вызове. Для удобства можно создать ScriptOptions, в котором можно будет добавить namespace-ы и reference-ы (можно также добавить статические классы, на подобии using static).
var options = ScriptOptions.Default
            .AddImports("System", "System.IO", "System.Collections.Generic",
                "System.Console", "System.Diagnostics", "System.Dynamic",
                "System.Linq", "System.Text",
                "System.Threading.Tasks")
            .AddReferences("System", "System.Core", "Microsoft.CSharp");
CSharpScript.Create("Console.WriteLine("Hello from script")", options);
Но здесь есть один момент — ScriptOptions почему-то не ограничивают доступные namespace-ы. Этакий whitelist, как я изначально подумал, возможно, просто не до конца разобрался.CSharpScript.RunAsync возвращает ScriptState, его можно дополнить вызвав ContinueWithAsync, который скомпилирует, выполнит код и вернет новый объект ScriptState. Можно повторно запустить скрипт, обратившись к свойству Script. Для получения результата, есть свойство ReturnValue.
ScriptState state = await CSharpScript.RunAsync("int x = 5;");
state = await state.ContinueWithAsync<int>("x + 1");
Console.WriteLine(state.ReturnValue); // 6
У объекта state можно посмотреть объявленные переменные, а так же полученный exception
foreach(var variable in state.Variables)
{
  Console.WriteLine($"{variable.Name} - {variable.Value}");
}
С помощью CSharpScript.Create можно создать делегат из скрипта, который будет запускать скрипт при вызове
var script = CSharpScript.Create<Func<int,int>>("x => x+1");
Console.WriteLine(await script.CreateDelegate().Invoke(1)); // 2
А так же, можно скомпилировать лямбда-выражение в виде строки, используя CSharpScript.EvaluateAsync (или другими способами, которые были выше)
var deleg = await CSharpScript.EvaluateAsync<Func<int, int>>("x => x * 2");
Console.WriteLine(deleg(5)); // 10
Это может быть удобно для сериализации и десериализации лямбда-выражений (Мой скудный ум не смог придумать юзкейса для этого, но я встречал людей, которым такая штука была нужна)Ниже представлены тесты:
Не думаю, что они очень точны, но помогут примерно понять скорость выполнения. С кодом можете ознакомится по ссылке.ЗаключениеMicrosoft.CodeAnalysis.CSharp.Scripting, довольно удобная шутка, для runtime выполнения C# кода. Можно использовать например в своем движке, или для предоставления способа модификации, без надобности создания .net проекта и его сборки.Топ 5 популярных реп в github, которые используют данный пакет:
Дополнительные примеры можно найти по ссылкам внизу:https://github.com/dotnet/roslyn/blob/main/docs/wiki/Scripting-API-Samples.mdhttps://github.com/dotnet/roslyn/tree/a7319e2bc8cac34c34527031e6204d383d29d4ab/src/ScriptingНадеюсь, моя первая статья не показалось слишком скучной, и я смог как-то вам помочь.Хорошего вам дня!
===========
Источник:
habr.com
===========

Похожие новости: Теги для поиска: #_.net, #_c#, #_.net, #_.net_core, #_c#, #_eval, #_roslyn, #_.net, #_c#
Профиль  ЛС 
Показать сообщения:     

Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы

Текущее время: 22-Ноя 20:22
Часовой пояс: UTC + 5