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






4.97/5 (16投票s)
本文介绍如何使用 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.CSharp 和 Roslyn.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 引擎执行。我处理了两种抛出错误的场景,这意味着用户输入对于引擎执行而言是无效的。
- 如果期望的返回值是字符串,我将返回该值,因为存在该值恰好是用户期望的可能性。
- 否则,将返回 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
函数时出现的。
按下制表键后
现在我们还可以访问所有库;
按下制表键后
自定义
目前,此实现可能有很多原因不符合您的需求。
- 可以将方法和变量添加到会话中,这可能不被接受。然而,这也可以被认为是一个很棒的功能。
- 用户可以访问框架的许多方法和属性。同样,这也可以被认为是一个很棒的功能。
- 可以添加引用(例如,“
using System.Linq;
”)。同样如此。 - 可以包含一些函数来帮助用户完成他们的工作(例如所有超越函数)。我认为这是使用 Roslyn 的一个绝佳优势。
- 可能希望强制用户输入全大写或全小写字母,以防止用户执行许多操作(由于大多数方法和类都是混合大小写,因此无法执行许多操作),并且可以使定义的函数不区分大小写。例如,我已经定义了函数‘
sin
’,但只有当用户输入全小写的函数时才能使用它。如果输入不强制为大写或小写,那么‘sin
’函数必须为所有大小写组合定义。 ConverterParameter
可用于提供额外的灵活性。
Roslyn 的缺点
与其他选项相比,Roslyn 对于此功能来说可能是最好的。然而,ScriptEngine
和 Session
对象目前似乎没有提供太多有用的方法和属性信息;我很难从这两个对象中获取信息。
对于正在执行的某些操作,例如此功能中发生的操作,可见性可用于限制用户输入,从而防止输入方法和引用,并且可见性可见于正在调用的方法,可以轻松修复大小写问题。
- 能够某种方式预处理用户输入,以便了解用户输入的用途,那将是很好的。例如,如果它正在定义一个方法:参数类型、名称和返回类型信息。然后,使用此输入对象作为
ScriptEngine
Execute
方法的参数而不是传递字符串,这将减少解析函数两次的开销,那将是很棒的。 - 我有点不解如何定义一个更复杂的方法,该方法可以像简单方程(例如,“4+5”)一样在会话对象上执行。我希望能够定义一个 lambda 表达式,然后执行该 lambda。这将包括用方括号定义的复杂方程(例如,“
(x,y) => {if (x > 0) return x; else return y;}
”)。我所想的是能够从字符串创建 lambda 对象,然后能够使用ScriptEngine
实例和Session
实例作为另一个参数来执行此 lambda 对象及其参数。 Session
对象(我怀疑当前类信息存储在此处)应该能够访问类中的所有项(方法、属性、字段等),包括已定义的类(如果用户执行了定义类的字符串)。- 作为另一项改进,应该可以扩展已经定义的类,但我不确定这是否可行。
Roslyn 显然还远未完成,因为它甚至没有被考虑用于 Visual Studio 的下一个版本,所以我希望 ScriptEngine
和 Session
对象能得到改进。
结论
该项目展示了如何使用 Roslyn 轻松地为 WPF 应用程序的用户提供一种方式,让他们通过绑定到数字和字符串值的 IValueConverter
接口以方程的形式输入值。
- 它可以进行自定义,添加可能为应用程序提供所需灵活性的函数。
- 输入基于 C#,因此开发人员非常熟悉其功能(不幸的是,这并不一定能帮助用户)。
该示例还提供了一种与 Roslyn 交互的方式,可以使用 Visual Studio 提供的调试工具来调查与引擎的交互(因此,尽情使用它来学习 Roslyn 的一个方面)。这一点无法通过 Visual Studio 中安装 Roslyn 后可用的“C# Interactive Window”来完成。
明显的缺点是它是一个 beta 产品,并且在相当长的一段时间内仍将如此,但我相信 Microsoft 最终会将该产品包含在发布版本中,在此之前它需要特殊安装。
另一个缺点是关于注入的担忧,因为 ScriptEngine
Execute
方法可以执行任何字符串。
我欢迎其他成员的意见,以帮助我更好地理解和使用 Roslyn 进行此应用程序。目前,CodeProject 上只有几篇关于 Roslyn 的文章,看到更多关于这个强大的 功能将是很好的。