Можно ли скомпилировать и выполнить новый код во время выполнения в .NET?

Примечание. В этом вопросе не рассматривается математическая оценка выражения. Я хочу скомпилировать и выполнить новый код во время выполнения в .NET. При этом ...

Я хотел бы разрешить пользователю вводить любое уравнение, например следующее, в текстовое поле:

x = x / 2 * 0.07914
x = x^2 / 5

И примените это уравнение к точкам входящих данных. Входящие точки данных представлены Икс, и каждая точка данных обрабатывается по заданному пользователем уравнению. Я сделал это много лет назад, но решение мне не понравилось, потому что оно требовало синтаксического анализа текста уравнения для каждого вычисления:

float ApplyEquation (string equation, float dataPoint)
{
    // parse the equation string and figure out how to do the math
    // lots of messy code here...
}

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

FunctionPointer foo = ConvertEquationToCode(equation);
....
x = foo(x);  // I could then apply the equation to my incoming data like this

Функция ConvertEquationToCode проанализирует уравнение и вернет указатель на функцию, которая применяет соответствующую математику.

Приложение в основном будет писать новый код во время выполнения. Возможно ли это с .NET?

Возможный дубликат Как я могу динамически оценивать выражение C#?

Ridkuma 26.04.2017 19:03
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
34
1
20 480
15
Перейти к ответу Данный вопрос помечен как решенный

Ответы 15

Вы можете попробовать посмотреть либо на CodeDom, либо на деревья лямбда-выражений. Я думаю, что любой из них должен позволить вам это сделать. Деревья выражений, вероятно, лучший вариант, но они также требуют более высокого обучения.

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

Вам нужен синтаксический анализатор выражений. Я сделал это один раз, преобразовав выражение из инфикса в постфикс, а затем используя парсер posfix на основе стека. Алгоритмы для обоих из них хорошо определены и существуют в Интернете и в книгах.

Nick 24.10.2008 20:25

Вы можете запустить здесь, и если вы действительно хотите в него войти, Бу можно изменить в соответствии с вашими потребностями. Вы также можете интегрировать LUA с .NET. Любые три из них можно использовать в составе делегата для вашего ConvertEquationToCode.

Да, определенно возможно, чтобы пользователь набрал C# в текстовое поле, а затем скомпилировал этот код и запустил его из вашего приложения. Мы делаем это в моей работе, чтобы иметь возможность настраивать бизнес-логику.

Вот статья (я еще не пробежал ее бегло), с которой вы должны начать:

http://www.c-sharpcorner.com/UploadFile/ChrisBlake/RunTimeCompiler12052005045037AM/RunTimeCompiler.aspx

Вы можете попробовать это: Calculator.Net

Он оценит математическое выражение.

Из публикации он подтвердит следующее:

MathEvaluator eval = new MathEvaluator();
//basic math
double result = eval.Evaluate("(2 + 1) * (1 + 2)");
//calling a function
result = eval.Evaluate("sqrt(4)");
//evaluate trigonometric 
result = eval.Evaluate("cos(pi * 45 / 180.0)");
//convert inches to feet
result = eval.Evaluate("12 [in->ft]");
//use variable
result = eval.Evaluate("answer * 10");
//add variable
eval.Variables.Add("x", 10);            
result = eval.Evaluate("x * 10");

Страница загрузки И распространяется под лицензией BSD.

Тогда вы захотите взглянуть на пространство имен CodeDom, можно скомпилировать код во время выполнения.

cfeduke 25.10.2008 18:30

В этой библиотеке есть какая-то ошибка, и, похоже, она до сих пор не поддерживается.

Philippe Lavoie 04.03.2011 21:35

Я сделал это с помощью CSharpCodeProvider, создав класс котельной пластины и функции в виде константной строки внутри моего класса генератора. Затем я вставляю код пользователя в котел и компилирую.

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

Если безопасность вообще вызывает беспокойство, я бы рекомендовал использовать деревья лямбда-выражений, но если нет, использование CSharpCodeProvider - довольно надежный вариант.

Вы также можете создать System.Xml.XPath.XPathNavigator из пустого «фиктивного» потока XML, и оценить выражения с помощью оценщика XPath:

static object Evaluate ( string xp )
{
  return _nav.Evaluate ( xp );
}
static readonly System.Xml.XPath.XPathNavigator _nav
  = new System.Xml.XPath.XPathDocument (
      new StringReader ( "<r/>" ) ).CreateNavigator ( );

Если вы хотите зарегистрировать переменные для использования в этом выражении, вы можете динамически создавать XML, который вы можете передать в перегрузке Evaluate который требует XPathNodeIterator.

<context>
  <x>2.151</x>
  <y>231.2</y>
</context>

Затем вы можете написать такие выражения, как "x / 2 * 0,07914", а затем x - это значение узла в вашем XML-контексте. Еще одна хорошая вещь - у вас будет доступ ко всем основным функциям XPath, который включает в себя математику и методы манипулирования строками и многое другое.

Если вы хотите пойти дальше, вы даже можете создать свой собственный XsltCustomContext (или написать здесь по запросу) где вы можете разрешать ссылки на функции и переменные расширения:

object result = Evaluate ( "my:func(234) * $myvar" );

my: func сопоставляется с методом C# /. NET, который принимает в качестве параметра значение типа double или int. myvar регистрируется как переменная в контексте XSLT.

Если ничего не помогает, в пространстве имен System.Reflection.Emit есть классы, которые можно использовать для создания новых сборок, классов и методов.

Однако одно предостережение. Мы использовали это на .Less (dotlesscss.com), и выдача кода обычно означает ужасную производительность.

Erik van Brakel 04.02.2010 03:55

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

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

Имеет ряд преимуществ. Например, если вы выполняете анализ «что, если» и хотите изменить значение для одного ввода за раз, вы можете пересчитать результаты, которые зависят от значения, которое вы изменили, сохраняя результаты, которые этого не сделали.

Попробуйте Vici.Parser: скачать здесь (бесплатно), это самый гибкий анализатор / вычислитель выражений, который я когда-либо встречал.

вы можете использовать system.CodeDom для генерации кода и его компиляции на лету посмотрите здесь

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

Вы видели http://ncalc.codeplex.com?

Он расширяемый, быстрый (например, имеет собственный кеш) позволяет вам предоставлять настраиваемые функции и переменные во время выполнения, обрабатывая события EvaluateFunction / EvaluateParameter. Примеры выражений, которые он может анализировать:

Expression e = new Expression("Round(Pow(Pi, 2) + Pow([Pi2], 2) + X, 2)"); 

  e.Parameters["Pi2"] = new Expression("Pi * Pi"); 
  e.Parameters["X"] = 10; 

  e.EvaluateParameter += delegate(string name, ParameterArgs args) 
    { 
      if (name == "Pi") 
      args.Result = 3.14; 
    }; 

  Debug.Assert(117.07 == e.Evaluate()); 

Он также изначально обрабатывает Unicode и многие типы данных. В комплекте идет напильник из оленьих рогов, если вы хотите поменять граммер. Также существует форк, который поддерживает MEF для загрузки новых функций.

Ответ принят как подходящий

Да! Использование методов из пространств имен Microsoft.CSharp, System.CodeDom.Compiler и System.Reflection. Вот простое консольное приложение, которое компилирует класс (SomeClass) с помощью одного метода (Add42), а затем позволяет вам вызывать этот метод. Это простой пример, который я отформатировал, чтобы полосы прокрутки не появлялись при отображении кода. Это просто демонстрация компиляции и использования нового кода во время выполнения.

using Microsoft.CSharp;
using System;
using System.CodeDom.Compiler;
using System.Reflection;

namespace RuntimeCompilationTest {
    class Program
    {
        static void Main(string[] args) {
            string sourceCode = @"
                public class SomeClass {
                    public int Add42 (int parameter) {
                        return parameter += 42;
                    }
                }";
            var compParms = new CompilerParameters{
                GenerateExecutable = false, 
                GenerateInMemory = true
            };
            var csProvider = new CSharpCodeProvider();
            CompilerResults compilerResults = 
                csProvider.CompileAssemblyFromSource(compParms, sourceCode);
            object typeInstance = 
                compilerResults.CompiledAssembly.CreateInstance("SomeClass");
            MethodInfo mi = typeInstance.GetType().GetMethod("Add42");
            int methodOutput = 
                (int)mi.Invoke(typeInstance, new object[] { 1 }); 
            Console.WriteLine(methodOutput);
            Console.ReadLine();
        }
    }
}

Однако это очень медленно для простых арифметических выражений.

Jonathan Nappee 11.02.2016 22:45

@JonathanNappee: Я был просто рад узнать, как это сделать, я не сказал, что это здорово. Я бы хотел увидеть ваши улучшения в производительности!

raven 12.02.2016 06:48

@ Джонатан Наппи, не могли бы вы пояснить свой комментарий? Я хочу убедиться, что этот метод добавления в «Add42» не медленнее, чем его встроенный в программу. Другими словами, ваш комментарий был больше нацелен на «метод, который добавляет 42, неэффективен», а не на «этот метод добавления кода в вашу программу во время выполнения неэффективен», правильно?

Derek 19.03.2018 19:39

@Derek Очень медленная часть заключается в необходимости скомпилировать, создать экземпляр и запустить Invoke. Это, вероятно, несколько сотен миллисекунд накладных расходов. простой арифметический синтаксический анализатор, вероятно, будет на порядок быстрее.

Jonathan Nappee 26.03.2018 19:47

@Jonathan Nappee Спасибо за объяснение. Чтобы убедиться, что я понимаю, как лучше всего использовать (или не использовать) это: если вы можете кэшировать результаты компиляции и повторно использовать их несколько раз, это потенциально может компенсировать накладные расходы на указанную компиляцию. Кроме того, для этого метода гораздо больше подходят сложные задачи, чем простая арифметика, для простой арифметики вам, вероятно, лучше будет что-то вроде Calculator.Net. Верны ли оба эти утверждения?

Derek 27.03.2018 00:41

@Derek Да, и то, и другое верно.

Jonathan Nappee 12.04.2018 16:07

Вот более современная библиотека для простых выражений: System.Linq.Dynamic.Core. Он совместим с .NET Standard / .NET Core, доступен через NuGet, и исходный код доступен.

https://system-linq-dynamic-core.azurewebsites.net/html/de47654c-7ae4-9302-3061-ea6307706cb8.htmhttps://github.com/StefH/System.Linq.Dynamic.Corehttps://www.nuget.org/packages/System.Linq.Dynamic.Core/

Это очень легкая и динамичная библиотека.

Я написал простой класс-оболочку для этой библиотеки, который позволяет мне делать такие вещи:

  string sExpression = "(a == 0) ? 5 : 10";
  ExpressionEvaluator<int> exec = new ExpressionEvaluator<int>(sExpression);
  exec.AddParameter("a", 0);
  int n0 = exec.Invoke();

После компиляции выражения вы можете просто обновить значения параметров и повторно вызвать выражение.

Другие вопросы по теме