65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Roslyn ScriptEngine 作为 ValueConverter 来处理用户输入

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.97/5 (16投票s)

2012年2月29日

CPOL

9分钟阅读

viewsIcon

51632

downloadIcon

741

本文介绍如何使用 Roslyn 在值转换器中评估用户输入。

引言

我创建了许多使用方程评估器的值转换器。一个转换器允许用户输入一个方程,然后由值转换器将其转换为绑定值(用于评估用户方程输入的值转换器),另一个转换器将 ConverterParameter 定义的方程应用于绑定值(使用 Converter Parameter 的 WPF 评估值绑定)。对于这两个项目,我都使用了 JavaScript 引擎中的 eval() 方法来评估方程。文章中提供了许多可用于评估值的引擎选项。文章的一些评论提到了其他我忽略的选项,包括使用 IronPython 引擎(不如 JavaScript 版本灵活)和 Microsoft Roslyn 引擎。

Roslyn 是 Microsoft 开放 VB 和 C# 编译器以支持“服务即编译器”的项目。

本文介绍如何使用 Roslyn 在值转换器中评估用户输入(这是使用 Roslyn 的最简单方法)。Roslyn 有许多使其非常适合此应用的特性。首先,与 JavaScript 不同,它基于框架,因此一切都与 WPF/Silverlight 环境兼容。其次,可以以用户友好的方式支持自定义函数。使用 JavaScript 的 eval() 方法,可以输入超越函数,但它们需要以“Math.”为前缀才能访问函数,并且函数名称的大小写必须正确。使用 Roslyn,可以创建只需函数名即可访问的函数。不幸的是,对于 C# 而言,函数名也是区分大小写的。

使用 Roslyn

有关 Roslyn 的信息和下载可以在 http://msdn.com/roslyn 找到。它包含指向论文和演示文稿的链接,包括 Anders Hejlsberg 关于 C# 和 Visual Basic 未来的一场精彩演示文稿。演示文稿的大约一半内容是关于 Roslyn 的。根据演讲,由于时间限制,Roslyn 将不会包含在 Visual Studio 2012 版本中。

CodeProject 上也有几篇关于 Roslyn 的好文章:在您的 .NET 应用程序中使用 Roslyn 作为脚本语言Roslyn CTP:三个入门项目

对于示例项目,必须下载并安装 Roslyn。然后必须添加两个引用:Roslyn.Compliers.CSharpRoslyn.Compliers。在“添加引用”对话框中添加这些引用(右键单击“引用”文件夹,然后单击“添加引用…”菜单项)。两者都可以在“扩展”列表中找到。

添加这些引用后,项目的引用文件夹将如下所示

实现

实现几乎与文章 Value Converter to Evaluate User Equation Input 中的值转换器相同,只是现在 Roslyn 引擎取代了 JavaScript 引擎。

class RoslynEvaluationValueConverter : IValueConverter
{
    private ScriptEngine rosylnEngine;
    private Session session;
    public object Convert(object value, Type targetType, 
      object parameter, System.Globalization.CultureInfo culture)
    {
      return value;
    }
 
    public object ConvertBack(object value, Type targetType, 
      object parameter, System.Globalization.CultureInfo culture)
    {
      if (rosylnEngine == null) 
        Initialize();
      if (value == null)
        return null;
      try
      {
        var x = rosylnEngine.Execute(value.ToString(), session);
          return x;
      }
      catch (Exception e)
      {
        return null;
      }
    }
 
    private void Initialize()
    {
      session = Session.Create();
      rosylnEngine = new ScriptEngine();
      rosylnEngine.Execute("using System;", session);
      rosylnEngine.Execute("double sin(double x) {return Math.Sin(x);} " + 
        "double tan(double x) {return Math.Tan(x);}", session);
      //rosylnEngine.Execute("double cos(double x) {return Math.Cos(x);}", session);
      rosylnEngine.Execute("double pi = 3.1415926535;", session);
    }
}

与 JavaScript 实现一样,Convert 方法基本上只返回该值,因为只需要处理用户输入值。在 ConvertTo 方法中,如果引擎和会话尚未初始化,则会进行初始化;然后,如果值不为 null,则用户输入将由 Roslyn 引擎执行。我处理了两种抛出错误的场景,这意味着用户输入对于引擎执行而言是无效的。

  1. 如果期望的返回值是字符串,我将返回该值,因为存在该值恰好是用户期望的可能性。
  2. 否则,将返回 null 给绑定变量。返回 null 对于许多用途实际上是好的,因为对于数字类型来说它是一个无效值。WPF 足够智能,能够知道当绑定值对于绑定的 Text 属性无效时,就会出现错误情况,并在 TextBox 上显示红色边框。

Initialize 方法的作用远不止初始化引擎和会话。首先,代码执行一个 using 语句,使 System 命名空间可用。此命名空间初始化也可以在实例化脚本引擎时完成。

rosylnEngine = new ScriptEngine(new Assembly[] {}, new string[] { "System" });

此代码还创建了两个可供用户使用的函数以及一个常量。定义“sin”函数使用户无需任何命名空间或类声明即可访问此超越函数。

注意:定义两个超越函数实际上需要 System 命名空间;基本数学在不添加此命名空间的情况下也能很好地工作。

在制表符之后

这也可以通过指定类和区分大小写的函数名来完成

按下制表键后

由于定义了“pi”,我们可以实际使用角度

注意:我们必须包含小数点以强制将其转换为浮点数,这很遗憾。当然,如果 pi 是“sin”函数参数的第一个参数,那就没有问题。(我认为编译器应该识别 sin 函数接受一个 double 值,并应相应地进行计算。)

按下制表键后

实现的一个有趣效果是,可以向引擎添加函数。

这会导致错误,因为没有返回值,但现在有了另一个可用的超越函数。

注意:TextBox 仍然有红色边框,这是在上一步定义 cosine 函数时出现的。

按下制表键后

现在我们还可以访问所有库;

按下制表键后

自定义

目前,此实现可能有很多原因不符合您的需求。

  1. 可以将方法和变量添加到会话中,这可能不被接受。然而,这也可以被认为是一个很棒的功能。
  2. 用户可以访问框架的许多方法和属性。同样,这也可以被认为是一个很棒的功能。
  3. 可以添加引用(例如,“using System.Linq;”)。同样如此。
  4. 可以包含一些函数来帮助用户完成他们的工作(例如所有超越函数)。我认为这是使用 Roslyn 的一个绝佳优势。
  5. 可能希望强制用户输入全大写或全小写字母,以防止用户执行许多操作(由于大多数方法和类都是混合大小写,因此无法执行许多操作),并且可以使定义的函数不区分大小写。例如,我已经定义了函数‘sin’,但只有当用户输入全小写的函数时才能使用它。如果输入不强制为大写或小写,那么‘sin’函数必须为所有大小写组合定义。
  6. ConverterParameter 可用于提供额外的灵活性。

Roslyn 的缺点

与其他选项相比,Roslyn 对于此功能来说可能是最好的。然而,ScriptEngineSession 对象目前似乎没有提供太多有用的方法和属性信息;我很难从这两个对象中获取信息。

对于正在执行的某些操作,例如此功能中发生的操作,可见性可用于限制用户输入,从而防止输入方法和引用,并且可见性可见于正在调用的方法,可以轻松修复大小写问题。

  1. 能够某种方式预处理用户输入,以便了解用户输入的用途,那将是很好的。例如,如果它正在定义一个方法:参数类型、名称和返回类型信息。然后,使用此输入对象作为 ScriptEngine Execute 方法的参数而不是传递字符串,这将减少解析函数两次的开销,那将是很棒的。
  2. 我有点不解如何定义一个更复杂的方法,该方法可以像简单方程(例如,“4+5”)一样在会话对象上执行。我希望能够定义一个 lambda 表达式,然后执行该 lambda。这将包括用方括号定义的复杂方程(例如,“(x,y) => {if (x > 0) return x; else return y;}”)。我所想的是能够从字符串创建 lambda 对象,然后能够使用 ScriptEngine 实例和 Session 实例作为另一个参数来执行此 lambda 对象及其参数。
  3. Session 对象(我怀疑当前类信息存储在此处)应该能够访问类中的所有项(方法、属性、字段等),包括已定义的类(如果用户执行了定义类的字符串)。
  4. 作为另一项改进,应该可以扩展已经定义的类,但我不确定这是否可行。

Roslyn 显然还远未完成,因为它甚至没有被考虑用于 Visual Studio 的下一个版本,所以我希望 ScriptEngineSession 对象能得到改进。

结论

该项目展示了如何使用 Roslyn 轻松地为 WPF 应用程序的用户提供一种方式,让他们通过绑定到数字和字符串值的 IValueConverter 接口以方程的形式输入值。

  • 它可以进行自定义,添加可能为应用程序提供所需灵活性的函数。
  • 输入基于 C#,因此开发人员非常熟悉其功能(不幸的是,这并不一定能帮助用户)。

该示例还提供了一种与 Roslyn 交互的方式,可以使用 Visual Studio 提供的调试工具来调查与引擎的交互(因此,尽情使用它来学习 Roslyn 的一个方面)。这一点无法通过 Visual Studio 中安装 Roslyn 后可用的“C# Interactive Window”来完成。

明显的缺点是它是一个 beta 产品,并且在相当长的一段时间内仍将如此,但我相信 Microsoft 最终会将该产品包含在发布版本中,在此之前它需要特殊安装。

另一个缺点是关于注入的担忧,因为 ScriptEngine Execute 方法可以执行任何字符串。

我欢迎其他成员的意见,以帮助我更好地理解和使用 Roslyn 进行此应用程序。目前,CodeProject 上只有几篇关于 Roslyn 的文章,看到更多关于这个强大的 功能将是很好的。

© . All rights reserved.