• .NET 动态脚本语言Script.NET 应用举例


    继续前面的两篇文章的动态脚本语言主题《.NET 动态脚本语言Script.NET 入门指南 Quick Start》和《.NET 动态脚本语言Script.NET 开发指南》,继续学习Script.NET的应用。

    类型定义 type definition

    Script.NET不是OOP的语言,但可以借助于属性(property bags)来模拟类型定义。

    vector = [
      x -> 4,
      y -> 3,
       length -> function() {
           Math.Sqrt(me.x^2+me.y^2);
       } 
      ];
    
    Console.WriteLine(vector.length());

    这段代码输出的结果是5,这里模拟向量类型的定义,并定义函数length计算向量的长度。me与C#中的this相似。

    再来看下面的例子,同样也是模拟OOP语言的类型定义

    stack = [
                 Test -> function() { return 'Hello Test';}
            ];
    return stack.Test();

    在Script.IDE中运行这段代码,它的结果是输出字符串Hello Test。

    在property bags中,用->来指向定义的函数名称,对比一下Script.NET语法规定的函数定义

    function swap(array, a, b)
    {
        tmp=array[a];
        array[a]=array[b];
        array[b]=tmp;
    }

    也可以这样的语法形式定义函数,和JavaScript函数定义相似

    Test = function (item) 
    {
         y = item;
    };
     

    函数的作用域 Function Scopes

    先看一下Script.NET官方文档中的一张解释scope的图片

    image

    程序语言中scope的作用是解析名称引用。比如,在C#中有如下的代码

    class Program
    {
          static void Main(string[] args)
          {    
                int x=100;
                Add();           
          }
    
          static int Add()
          {
               int x=10;
               return x;
          }
    }

    通过运行代码,可以知道Add方法(method,也可叫function)返回的值是10。C#是纯粹pure的OOP语言,没有全局变量。C语言则可以包涵全局变量,它在解析名称引用(scope)方面会复杂很多。抱歉,对C语言相当陌生,不能举例说明。

    来看几个例子,加深对scope的理解。

    a = 4; b = 2; c = 3;
    function test1(a,b)    global(c)
    {
        c = a+b;
        a = 15;
    }
    Console.WriteLine(test1(2,3));
    Console.WriteLine( a);
    Console.WriteLine( b);
    Console.WriteLine( c);

    这次的函数定义稍微有些改变,在函数的参数列表之后,加了global(c) 以表示它的的作用域是global,全局的。

    在Script.NET IDE中运行,结果如下

    15
    4
    2
    5

    这里先说明一下,函数如果没有定义return语句,则最后一个表达式的值,就是函数的返回值,所以test1(2,3)返回a=15;结果就是15。a和b的scope是local,在function中对它的改变,不会影响到它的值(这和C语言的堆栈定义,调用函数,取参数的副本,并把参数压入堆栈相同,如果要传入参数变量的引用,而不是副本,则要用到指针)。c因为定义为global,在函数test1中对它的改变,仍然会保留,所以最后一行输出结果5。

    如果没有忘记前面的contract部分的内容,应该可以理解下面的函数定义格式。

    a = 4; b = 2; c = 3;
    function test1(a,b)   global(c)
    [
        pre(a>0);
        post();
        invariant();
    ]
        {
            c = a+b;
            a = 15;
        }

    如果在函数中应用global,却没有定义它的变量,像下面的语句,则会抛出异常ScriptIdNotFoundException

    Test = function (item) global(y)
         {
                   y = item;
          };
    Test(2);

    在C语言中,一定要先定义函数,再来调用函数,否则会找不到函数定义。如果要在定义函数的语句之前调用该函数,则需要前置声明。Visual C++中还要求在.h文件中声明类型和函数,在.cpp文件中实现,C#中则没有这个限制。

    请看下面的代码

    return f();
    
    function f() {return 1;}

    在定义函数f的语句代码之前,可以调用它而不用写任何语句,多亏了编译技术的进步。

    运算符指派 Operators Dispatching

    在程序语言中,如果运算符号(+,-%…)左右两边的类型不相同,则会发生类型转换,这还涉及到运算符号重载,比如加号+可以应用到数据运算,也可以应用于字符串连接。请体会下面的例子来理解运算符指派。

    Console.WriteLine( 1+1);                //2
    Console.WriteLine( 1.2+1 );              //2.2  
    Console.WriteLine( '1'+1 );              //11
    Console.WriteLine( 'Hello '+1+' Text' ); //Hello 1 Text
    Console.WriteLine( 10-1);       //9
    Console.WriteLine( 1.2-1);      //1.2-1=0.2
    Console.WriteLine( 10*12);      //10*12
    Console.WriteLine( 3.2*3);      //(double)3.2*3=9.6
    Console.WriteLine( 3.5*21.5);   //(double)3.5 * 21.5=75.25
    Console.WriteLine( 6/2);        //6 / 2 =3
    Console.WriteLine( 10/12);      //10 / 12 =0
    Console.WriteLine( 45.43/12.3); //(double)45.43 / 12.3=.69349593495935
    Console.WriteLine( 3.5/21);     //(double)3.5 / 21=0.166666666666667
    Console.WriteLine( 3/21.2);     //3 / (double)21.2=0=0.141509433962264
    Console.WriteLine( 6%2);        //6 % 2=0
    Console.WriteLine( 10%12);      //10 % 12=10
    Console.WriteLine( 45.43%12.3); //(double)45.43 % 12.3=8.53
    Console.WriteLine( 6^2);        //Math.Pow(6, 2)=36    

    如果运算符号不支持两边的数据类型,则发抛出异常NotSupportedException。比如

    Console.WriteLine( '1'-1 );  //NotSupportedException
     

    Scripting Runtime 运行时

    Script.NET的作者画了一张漂亮的Runtime图,以解释他们的作用。

    Runtime

    这个图可以解释下面的代码,为什么不需要添加类型Test的定义,也可以找到Test类型
    //RuntimeHost.AddType("TestT", typeof(TestT));
    Test rez = (Test)Script.RunCode(@"
             a = new Test();
             a.value = 'test';
             a.intVal = 20;
    
             return a; ")

    原因就在AssemblyManager,它会sacn当前app domain中的程序集以及其中的类型定义,加入到Script Runtime中。

    如果还记得前面文章中提到的RuntimeConfig.xml文件,它会在runtime 启动时,加载需要的程序集,类型映射,和初试代码片段。可以通过修改ScriptDotNet项目中的这个Embedded Resource的文件,达到初试化的目的。

    image

    也可以用下面的方法,加载自定义的runtime configuration。

    public static Stream TestConfig
    {
          get
          {
            Stream configStream = Assembly.GetExecutingAssembly().GetManifestResourceStream                                ("UnitTests.TestConfig.xml");
            configStream.Seek(0, SeekOrigin.Begin);
            return configStream;
          }
    }
    
    RuntimeHost.AssemblyManager = new BaseAssemblyManager();
    RuntimeHost.Initialize(TestConfig);

    RuntimeHost.Initialize();这一句是必须调用的,传入Stream对象,还可以这样调用

    RuntimeHost.Initialize(new FileStream("RuntimeConfig.xml",FileMode.Open));

    可以像下面的代码定义,这样阻止load程序集

    RuntimeHost.AssemblyManager.BeforeAddAssembly +=
        (s, e) => {
           if (e.Assembly.FullName != "System.Data, Version=2.0.0.0, Culture=neutral,                                                            PublicKeyToken=b77a5c561934e089")
                throw new Exception();
            };
     

    自定义运算符 Custom operator

    再来看一个运算符的例子,我希望1234,$1234,都可以表示货币数量,$这个符号没有内置在系统的运算符中,需要用下面的方法,添加进来。

    private class DollarHandler : IOperatorHandler
    {    
          public object Process(HandleOperatorArgs args)
          {
            if (args.Arguments != null && args.Arguments.Length == 1)
            {
              string value = (string)args.Arguments[0];
              args.Cancel = true;
              return int.Parse(value);
            }
            throw new NotSupportedException();
        }
    }
     
    RuntimeHost.Initialize(TestConfig);
    RuntimeHost.RegisterOperatorHandler("$", new DollarHandler());
    
    Script script = Script.Compile(@"
                  return $'1234';   ");
    object rez = script.Execute();

    通过Debug代码,可以看到rez的值是1234,没有抛出异常。

    扩展类型的函数 Custom type’s function

    再看下面的例子,Script.NET内置的类型没有ToString函数,我要给它扩展,添加ToString函数。

    private class MyBinder : ObjectBinder
        {
          public override bool CanBind(MemberInfo member)
          {
              if (member.Name == "ToString")
                  return true;         
            return base.CanBind(member);
          }
          public object ConvertTo(object value, Type targetType)
          {
              return value.ToString();
          }
    }

    再来举例调用上面添加的函数

    RuntimeHost.Binder = new MyBinder();
    RuntimeHost.Initialize(TestConfig);
    
    Script script = Script.Compile(@"
                b = 'a';
                b.ToString();
                money=100;
                money.ToString();
           ");
    object obj=script.Execute();

    返回结果obj的值是100。这使我想到了C#的扩展方法,如下所示

    public class DecimalHelper
     {
            public static string ToString(this decimal obj)
            {
                return obj.ToString();
            }
     }

    下面这样的代码是可以通过编译的

    decimal money = 100;
    string howMuch=money.ToString();

    这两者非常的相似。C#中的decimal有对象System.Decimal来表达同等的含义,而Script.NET没有对象的概念,只有基础的数据类型double,long,string,bool和数组array,同样可以做到扩展函数的功能。

  • 相关阅读:
    Linux下的文件批量转换为UTF8编码-enca
    【转】valgrind的简介以及安装
    springboot2.0整合logback日志(详细)
    springboot整合redis
    用Thymeleaf在实际项目中遇到的坑
    RedisTemplate和StringRedisTemplate的区别
    @EnableCircuitBreaker熔断超时机制
    eclipse转到idea过程中的基本设置...
    java.lang.NoSuchMethodError
    springcloud服务提供producer and 服务调用consumer
  • 原文地址:https://www.cnblogs.com/JamesLi2015/p/2177542.html
Copyright © 2020-2023  润新知