.NET 动态脚本语言Script.NET 开发指南
前一篇文章介绍了《.NET 动态脚本语言Script.NET 入门指南 Quick Start》的基础知识,这一篇文章继续介绍Script.NET开发相关的内容。
Script.NET IDE Environment
以前提到的书写Script.NET脚本的方式,在Visual Studio中书写代码,然后以调试的方式运行代码。这种方式适合于对Script.NET不熟悉,或是发生了不可理解的错误后,才运用Visual Studio强大的调试功能查找问题。Script.NET也考虑到了开发脚本的环境,已经提供了一个IDE。
语法高亮显示,解析与运行脚本,输出结果到Output窗口中。这对于日常的脚本开发,已经够用。
如果能给它配置上.NET Framework的智能提示,则会更加完美。
Contract 契约
Script.NET是动态的语言,从做Web开发中经常用到的JavaScript 一样,动态语言不需要在定义时指明变量的类型,而是在运行时,根据值来分配类型。
先介绍一下Script.NET的异常处理,基本的结构和.NET一样
try { …… } catch(e) { …… } finally { …… }
再看动态语言的例子,比教下面两个例子,就帮助理解它的”动态”含义。
再来换一下参数,同样调用方法inc,代码是这样的
function inc(a) { a=a+1; return a; } Console.WriteLine(inc('abc'));
Output窗口中输出的结果是abc1。这个例子解释了动态语言的主要区别,在运行时判断类型。
如果要求函数inc只能接受int类型的变量,则需要这样处理
function inc(a) [ pre(a is int); post(); invariant(); ] { a=a+1; return a; } Console.WriteLine(inc(10));
如果再次用Console.WriteLine(inc('abc'));调用加了contract的inc函数,则会抛出script exception。
这就是契约的作用,它的完整定义是这样的,在函数定义体body前加三个表达式
[ pre(expression); post(expression); invariant(expression); ]
再来看contract中post的处理。来看下面的脚本
function inc(a) [ pre(a is int); post(a<5); invariant(); ] { a=a+1; return a; } try { Console.WriteLine(inc(10)); } catch(e) { Console.WriteLine(e); } finally { }
F5运行代码,会看到Output窗口中输出异常Post condition for function call failed。
如果用inc(3)调用,则不会抛出异常。从这个例子中,可以理解pre可以用来约束参数类型,post可以约束函数返回结果
使用脚本创建.NET应用程序
在前一节的入门中提到,Script可以引用Host的变量,并对它进行操作,Host也可以获取到Script中运行的结果。把场景换成是WinForms中的Form窗体,像下面的这个应用程序,则很容易实现。
把当前的窗体MainForm传入到Script中,Script脚本中可对传入的变量Form进行操作,设置Text属性。
.NET Integration .NET集成
Script.NET可以使用.NET Framework现有的类型,以提供绝大部分场合需要的类型。
sin = Math.Sin; Console.WriteLine(sin(0.75)); Console.WriteLine(DateTime.Now); a='1,2,3,4'; array=Regex.Split(a,','); foreach(a in array) Console.WriteLine(a);
在使用.NET的正则表式Regex类型,都不需要using,直接拿来即用。
像上面的Regex.Split是静态方法,如果是实例子方法,可使用using指令简化Script.NET脚本代码
using (Math) { //Build-in Objects //using the Pow function from Math class a = Pow(2, 3); Console.WriteLine(a); }
Script.NET内置了三个函数,
eval – 评估表达式的值并返回(evaluates value of an expression) ,例子 a = eval('2+3*4');
clear – 清空上下文变量(clears all variables in context),已经过时
array - 创建数组(creates typed array of objects)
如果需要扩展Script.NET的内置函数,可参考这个例子
class Program { static void Main(string[] args) { RuntimeHost.Initialize(); Script script = Script.Compile(@" return Inc(10); "); script.Context.SetItem("Inc", new IncrementFunction()); object result = script.Execute(); Console.WriteLine(result); Console.ReadLine(); } } public class IncrementFunction : IInvokable { public bool CanInvoke() { return true; } public object Invoke(IScriptContext context, object[] args) { int obj =Convert.ToInt32(args[0]); return ++obj; } }
经过运算之后,result的结果是11。经过这样的处理,以后需要应用Inc函数的地方,都可直接调用。
没有动态语言,而只用动态编译
我想,我还是得举例说明一下,Script.NET动态语言的好处,用途,这样它的价值才能得到体现。
我在做工作流的表达式语句判断时,有这样一段代码
SalesOrder order = new SalesOrder("OE0913", 100); Rule dr = new Rule("this.OrderNo == \"OE0913\"", "Rule1", new Statement[] { new Assignment("this.Failed", "true"), }, new Statement[0] ); Parser parser = new Parser(); parser.Fields.Add("_orderNo"); parser.Fields.Add("_failed"); RuleSet drs = new RuleSet(); drs.RuleTypes.Add(new RuleTypeSet(typeof(SalesOrder), new Rule[] { dr })); drs.Eval(parser); bool pass = order.Failed; drs.RunRules(order); pass = order.Failed;
它要表达的含义就是条件语句
IF OrderNo =’OE0913’
THEN Failed=true;
上面的.NET代码,是对这条规则语句的.NET封装。
工作流引擎接受一个变量,代表当前的对象,比如这里的SalesOrder。我在代码中定义了一个IF ELSE 语句块,当OrderNo为OE0913时,它会运行语句this.Failed=true,否则跳过,之后,parser解析器加上两个环境变量_orderNo和_failed的值,并引用到上面的规则。第一次,取到的值是order.Failed,这个值是引擎直接传递过来的,接着的一句drs.RunRules(order),会传入对象SalesOrder(“OE0913”,100)并且运行规则,因符合规则this.OrderNo=’OE0913’,满足条件,运行语句this.Failed=true, 最后一句获取到的值pass=order.Failed,就是经过引擎运算之后的值。这几句代码,是我的工作流引擎中自定义规则编辑器的单元测试代码,可以帮助你理解。
抱歉,如果你不能明白这个例子,请参考文章《信息化基础建设 工作流开发》,或者实现一个工作流的自定义规则编辑器,应该可以明白这几句代码的含义。试想一下,在没有动态语言的情况下,实现一个IF ELSE的条件,是相当困难的。
代码片段 Code Snippet
在熟悉了基础的Script.NET的语法后,你可能需要下面的代码片断作为参考,来实现自己的脚本逻辑。
for 语句
z = 0;
for(i=0; i<10; i++)
{
z += 2;
}
foreach 语句
a=[1,2,3,4]; s=0; foreach(c in a) { s+=c; }
访问SQL Server数据库
sql = DbProviderFactories.GetFactory("System.Data.SqlClient"); connection = sql.CreateConnection(); connection.ConnectionString = "Data Source=(local);Initial Catalog=Northwind;Integrated Security=True"; connection.Open(); command = sql.CreateCommand(); command.Connection = connection; command.CommandText = "select * from Customers"; reader = command.ExecuteReader(); while (reader.Read()) { Console.WriteLine(reader["CompanyName"]+". "+ reader["ContactName"]); } connection.Dispose();
操作Windows Forms窗体
f = new Form(); f.Width = 320; f.Height = 240; f.Text = 'Script Demo'; f.Show(); g = Graphics.FromHwnd(f.Handle); for (i = 0; i<10; i++) g.DrawRectangle(Pens.Red, new Rectangle(10+i*10 , 10+i*10, 290-i*20, 180-i*20 ) ); System.Threading.Thread.Sleep(1500);
访问网络,读取RSS聚合
a = new XmlDocument(); a.Load("http://protsyk.com/cms/?feed=rss2&cat=10"); foreach (n in a.SelectNodes('/rss/channel/item')) Console.WriteLine(n['title'].InnerText + ' ' + n['link'].InnerText);
泛型数组
v = new int[10]; for(i=0; i<v.Length; i++) v[i] = i; s = ''; foreach(i in v) s = s + i + ' '; a = new List<|string|>[10]; a[0] = new List<|string|>(); a[0].Add('Hello'); b = a[0].ToArray(); c = b[0];
递归(斐波纳契,最大公约数)
function fib(n){ if (n==1) return 1; else if (n==2) return 2; else return fib(n-1)+fib(n-2); }
function fac(n){ if (n==1) return 1; else return n*fac(n-1); }
function GCD(a,b){ if (a>b) return GCD(a-b,b); else if (b>a) return GCD(a,b-a); else return a; }
关于动态语言
一开始看Script.NET,和.NET的动态编译源代码很像,经过这两篇文章的学习,发现它已经远远超过了.NET动态编译的能力,从语法,编译处理到Host与Script之前的交互能力,应该与Iron,Python之类的动态语言相提并论。虽然文档不完整,有的部分已经过时(Obsolete),可以从它的UnitTest项目开始熟悉和了解它,Example项目文件夹也包含很多脚本例子。UnitTest项目既是单元测试的好工具,也是一份给开发人员看的文档,非常有价值。