【问题标题】:Is it possible to interpret a C# expression tree to emit JavaScript?是否可以解释 C# 表达式树以发出 JavaScript?
【发布时间】:2011-08-25 08:21:11
【问题描述】:

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

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

有什么东西会遍历表达式树并生成它吗?

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

【问题讨论】:

    标签: javascript c# transformation expression-trees


    【解决方案1】:

    这可能并不容易,但是是的,这是绝对可行的。 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) 等。

    【讨论】:

      【解决方案2】:

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

      【讨论】:

      • 谢谢,'traverse' 和 'generate' 是更准确的动词,我已经更新了问题
      【解决方案3】:

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

      【讨论】:

      • +0...实际上 Script# 是相当完整的交叉编译器...它确实转换了 C# 源代码并且不能轻易地用于转换 Expression 对象。
      • 我的理解是,OP 将 Expression 对象用作是否可以将 C# 代码编译为 JavaScript 的理论示例,而不是将它们用作实际实现。
      【解决方案4】:

      一些人开发了开源库来解决这个问题。我一直在看的是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 源代码所看到的,在处理每个案例以正确生成代码方面需要付出很多努力。

      【讨论】:

        【解决方案5】:

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

        更多示例here

        【讨论】:

          猜你喜欢
          • 2015-04-20
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多