Например, если у вас есть выражение, подобное этому:
Expression<Func<int, int>> fn = x => x * x;
Есть ли что-нибудь, что пересечет дерево выражений и сгенерирует это?
"function(x) { return x * x; }"
Например, если у вас есть выражение, подобное этому:
Expression<Func<int, int>> fn = x => x * x;
Есть ли что-нибудь, что пересечет дерево выражений и сгенерирует это?
"function(x) { return x * x; }"
Это, вероятно, нелегко, но да, это абсолютно возможно. ORM, такие как Entity Framework или Linq to SQL, делают это для перевода запросов Linq в SQL, но вы можете генерировать все, что хотите, из дерева выражений...
Вы должны реализовать ExpressionVisitor
для анализа и преобразования выражения.
EDIT: здесь очень простая реализация, которая работает для вашего примера:
Expression<Func<int, int>> fn = x => x * x;
var visitor = new JsExpressionVisitor();
visitor.Visit(fn);
Console.WriteLine(visitor.JavaScriptCode);
...
class JsExpressionVisitor : ExpressionVisitor
{
private readonly StringBuilder _builder;
public JsExpressionVisitor()
{
_builder = new StringBuilder();
}
public string JavaScriptCode
{
get { return _builder.ToString(); }
}
public override Expression Visit(Expression node)
{
_builder.Clear();
return base.Visit(node);
}
protected override Expression VisitParameter(ParameterExpression node)
{
_builder.Append(node.Name);
base.VisitParameter(node);
return node;
}
protected override Expression VisitBinary(BinaryExpression node)
{
base.Visit(node.Left);
_builder.Append(GetOperator(node.NodeType));
base.Visit(node.Right);
return node;
}
protected override Expression VisitLambda<T>(Expression<T> node)
{
_builder.Append("function(");
for (int i = 0; i < node.Parameters.Count; i++)
{
if (i > 0)
_builder.Append(", ");
_builder.Append(node.Parameters[i].Name);
}
_builder.Append(") {");
if (node.Body.Type != typeof(void))
{
_builder.Append("return ");
}
base.Visit(node.Body);
_builder.Append("; }");
return node;
}
private static string GetOperator(ExpressionType nodeType)
{
switch (nodeType)
{
case ExpressionType.Add:
return " + ";
case ExpressionType.Multiply:
return " * ";
case ExpressionType.Subtract:
return " - ";
case ExpressionType.Divide:
return " / ";
case ExpressionType.Assign:
return " = ";
case ExpressionType.Equal:
return " == ";
case ExpressionType.NotEqual:
return " != ";
// TODO: Add other operators...
}
throw new NotImplementedException("Operator not implemented");
}
}
Он обрабатывает только lambdas с одной инструкцией, но в любом случае компилятор С# не может генерировать дерево выражений для блока lambda.
По-прежнему очень много работы, это очень минимальная реализация... вам, вероятно, нужно добавить вызовы методов (VisitMethodCall
), доступ к свойствам и полям (VisitMember
) и т.д.
Script # используется внутренними разработчиками Microsoft для выполнения именно этого.
Взгляните на Lambda2Js, библиотеку, созданную Мигелем Анджело для этой цели.
Он добавляет метод расширения CompileToJavascript
к любому выражению.
Пример 1:
Expression<Func<MyClass, object>> expr = x => x.PhonesByName["Miguel"].DDD == 32 | x.Phones.Length != 1;
var js = expr.CompileToJavascript();
Assert.AreEqual("PhonesByName[\"Miguel\"].DDD==32|Phones.length!=1", js);
Пример 2:
Expression<Func<MyClass, object>> expr = x => x.Phones.FirstOrDefault(p => p.DDD > 10);
var js = expr.CompileToJavascript();
Assert.AreEqual("System.Linq.Enumerable.FirstOrDefault(Phones,function(p){return p.DDD>10;})", js);
Дополнительные примеры здесь.
Выражение уже проанализировано для вас компилятором С#; все, что остается, - это перемещение дерева выражений и генерация кода. Прохождение дерева можно выполнить рекурсивно, и каждый node можно обработать, проверив, какой он тип (существует несколько подклассов Expression
, представляющих, например, функции, операторы и поиск членов). Обработчик для каждого типа может генерировать соответствующий код и пересекать дочерние элементы node (которые будут доступны в разных свойствах, в зависимости от того, какой тип выражения он есть). Например, функцию node можно обработать, сначала выведя "функцию" ( "далее следует имя параметра, за которой следует" ) { ". Затем тело может быть обработано рекурсивно, и, наконец, вы выводите" }".
Несколько человек разработали библиотеки с открытым исходным кодом, стремящиеся решить эту проблему. Тот, который я смотрел, Linq2CodeDom, который преобразует выражения в граф CodeDom, который затем может быть скомпилирован на JavaScript, пока код совместим.
Script # использует исходный исходный код С# и скомпилированную сборку, а не дерево выражений.
Я внес некоторые незначительные изменения в Linq2CodeDom, чтобы добавить JScript в качестве поддерживаемого языка - по существу просто добавив ссылку на Microsoft.JScript, обновив перечисление и добавив еще один случай в GenerateCode. Вот код для преобразования выражения:
var c = new CodeDomGenerator();
c.AddNamespace("Example")
.AddClass("Container")
.AddMethod(
MemberAttributes.Public | MemberAttributes.Static,
(int x) => "Square",
[email protected]<int, int>(x => x * x)
);
Console.WriteLine(c.GenerateCode(CodeDomGenerator.Language.JScript));
И вот результат:
package Example
{
public class Container
{
public static function Square(x : int)
{
return (x * x);
}
}
}
Подпись метода отражает более строго типизированный характер JScript. Может быть лучше использовать Linq2CodeDom для генерации С#, а затем передать это на Script #, чтобы преобразовать это в JavaScript. Я считаю, что первый ответ является наиболее правильным, но, как вы можете видеть, просмотрев источник Linq2CodeDom, при обработке каждого случая требуется много усилий, чтобы правильно генерировать код.