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

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

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

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

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

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?

Ответ 1

Да! Используя методы, найденные в 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();
        }
    }
}

Ответ 2

Вы можете попробовать следующее: 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.

Ответ 3

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

Вот статья (я не более, чем ее снял), которая должна вас запустить:

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

Ответ 4

Вы также можете создать System.Xml.XPath.XPathNavigator из пустого XML-потока "dummy" и оценивать выражения с помощью оценщика 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 это значение node в вашем XML-контексте. Еще одна хорошая вещь: у вас будет доступ ко всем основным функциям XPath, который включает в себя методы математики и струнных манипуляций и многое другое.

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

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

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

Ответ 5

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

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

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

Ответ 6

Вы видели 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 и многие типы данных изначально. Он поставляется с файлом antler, если вы хотите изменить грамматик. Существует также вилка, которая поддерживает MEF для загрузки новых функций.

Ответ 7

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

Ответ 8

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

Ответ 10

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

Ответ 11

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

Ответ 12

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

Ответ 13

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

Ответ 14

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

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

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