前一篇文章介绍了《.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项目既是单元测试的好工具,也是一份给开发人员看的文档,非常有价值。