是否可以解释 C# 表达式树以发出 JavaScript?

发布于 2024-11-07 08:03:28 字数 193 浏览 4 评论 0原文

例如,如果您有这样的表达式:

Expression<Func<int, int>> fn = x => x * x;

是否有任何东西可以遍历表达式树并生成它?

"function(x) { return x * x; }"

For example, if you have an expression like this:

Expression<Func<int, int>> fn = x => x * x;

Is there anything that will traverse the expression tree and generate this?

"function(x) { return x * x; }"

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

孤独患者 2024-11-14 08:03:29

C# 编译器已经为您解析了该表达式;剩下的就是遍历表达式树并生成代码。可以递归地遍历树,并且可以通过检查每个节点的类型来处理它(Expression 有多个子类,表示例如函数、运算符和成员查找)。每种类型的处理程序可以生成适当的代码并遍历节点的子节点(根据其表达式类型,这些代码将在不同的属性中可用)。例如,可以通过首先输出“function(”,后跟参数名称,后跟“) {”来处理函数节点。然后,可以递归地处理正文,最后输出“}”。

The expression has already been parsed for you by the C# compiler; all that remains is for you to traverse the expression tree and generate the code. Traversing the tree can be done recursively, and each node could be handled by checking what type it is (there are several subclasses of Expression, representing e.g. functions, operators, and member lookup). The handler for each type can generate the appropriate code and traverse the node's children (which will be available in different properties depending on which expression type it is). For instance, a function node could be processed by first outputting "function(" followed by the parameter name followed by ") {". Then, the body could be processed recursively, and finally, you output "}".

三寸金莲 2024-11-14 08:03:29

一些人开发了开源库来寻求解决这个问题。我一直在研究的是Linq2CodeDom,它将表达式转换为CodeDom图,然后可以将其编译为JavaScript 只要代码兼容即可。

Script# 利用原始 C# 源代码和编译后的程序集,而不是表达式树。

我对 Linq2CodeDom 进行了一些小的编辑,以将 JScript 添加为受支持的语言 — 本质上只是添加对 Microsoft.JScript 的引用、更新枚举,并在GenerateCode 中添加一个案例。下面是转换表达式的代码:

var c = new CodeDomGenerator();
c.AddNamespace("Example")
    .AddClass("Container")
    .AddMethod(
        MemberAttributes.Public | MemberAttributes.Static,
        (int x) => "Square",
        Emit.@return<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 生成 C#,然后将其传递给 Script# 以将其转换为 JavaScript。我相信第一个答案是最正确的,但正如您通过查看 Linq2CodeDom 源代码所看到的,在处理每种情况以正确生成代码方面需要付出很多努力。

A few people have developed open source libraries seeking to solve this problem. The one I have been looking at is Linq2CodeDom, which converts expressions into a CodeDom graph, which can then be compiled to JavaScript as long as the code is compatible.

Script# leverages the original C# source code and the compiled assembly, not an expression tree.

I made some minor edits to Linq2CodeDom to add JScript as a supported language--essentially just adding a reference to Microsoft.JScript, updating an enum, and adding one more case in GenerateCode. Here is the code to convert an expression:

var c = new CodeDomGenerator();
c.AddNamespace("Example")
    .AddClass("Container")
    .AddMethod(
        MemberAttributes.Public | MemberAttributes.Static,
        (int x) => "Square",
        Emit.@return<int, int>(x => x * x)
    );
Console.WriteLine(c.GenerateCode(CodeDomGenerator.Language.JScript));

And here is the result:

package Example
{
    public class Container
    {
        public static function Square(x : int)
        {
            return (x * x);
        }
    }
}

The method signature reflects the more strongly-typed nature of JScript. It may be better to use Linq2CodeDom to generate C# and then pass this to Script# to convert this to JavaScript. I believe the first answer is the most correct, but as you can see by reviewing the Linq2CodeDom source, there is a lot of effort involved on handling every case to generate the code correctly.

巷雨优美回忆 2024-11-14 08:03:28

这可能并不容易,但是,是的,这绝对可行。像 Entity Framework 或 Linq to SQL 这样的 ORM 可以将 Linq 查询转换为 SQL,但实际上您可以从表达式树中生成任何您想要的内容...

您应该实现一个 ExpressionVisitor 来分析和转换表达式。


编辑:这是一个适用于您的示例的非常基本的实现:

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");
    }
}

它仅使用单个指令处理 lambda,但无论如何 C# 编译器无法为块 lambda 生成表达式树。

当然,还有很多工作要做,这是一个非常小的实现...您可能需要添加方法调用 (VisitMethodCall)、属性和字段访问 (VisitMember) 等

It's probably not easy, but yes, it's absolutely feasible. ORMs like Entity Framework or Linq to SQL do it to translate Linq queries into SQL, but you can actually generate anything you want from the expression tree...

You should implement an ExpressionVisitor to analyse and transform the expression.


EDIT: here's a very basic implementation that works for your example:

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");
    }
}

It only handles lambdas with a single instruction, but anyway the C# compiler can't generate an expression tree for a block lambda.

There's still a lot of work to do of course, this is a very minimal implementation... you probably need to add method calls (VisitMethodCall), property and field access (VisitMember), etc.

行雁书 2024-11-14 08:03:28

Microsoft 内部开发人员使用 Script# 来执行此操作。

Script# is used by Microsoft internal developers to do exactly this.

尽揽少女心 2024-11-14 08:03:28

看一下 Lambda2Js,这是一个由 Miguel Angelo 就是为了这个目的。

它向任何表达式添加了一个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);

更多示例此处

Take a look at Lambda2Js, a library created by Miguel Angelo for this exact purpose.

It adds a CompileToJavascript extension method to any Expression.

Example 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);

Example 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);

More examples here.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文