7.7 脚本语言访问C#规范

Limax提供limax.util.ReflectionCache类,为脚本语言访问C#对象提供尽量完整的支持。当前由CLR/Lua,CLR/Javascript使用。


  • 7.7.4 属性访问

    • 读属性

      GetValue时,name参数为string类型,值与当前对象的属性名匹配,执行属性读取,如果属性不可get,返回DBNull.Value。


    • 写属性

      SetValue时,name参数为string类型,值与当前对象的属性名匹配,用value写属性,脚本语言提供的value类型可能与属性实际类型不匹配,所以必须先执行类型转换,类型转换有失败抛出异常的可能。如果属性不可set,操作被忽略。


  • 7.7.5 方法访问

    • 读方法

      GetValue时,name参数为string类型,值与当前对象的方法名匹配,使用一个实现Invokable接口的内部对象包装方法集合(方法重载允许同名方法的存在)并返回。


    • 写方法

      SetValue时,name参数为string类型,值与当前对象的方法名匹配,该操作被忽略。


  • 7.7.6 索引化属性访问

    脚本语言的语法限制了只能访问单参数索引化属性。

    • 读索引化属性

      GetValue时, 如果name参数为string类型, 并且name的值不匹配任何可访问的字段名, 属性名, 方法名, 则把name假设为索引参数, 执行后续访问, 这样的访问存在二义性。name参数为其它类型, 则直接认定为索引参数, 遍历所有单参数可读索引属性, 匹配类型进行访问, 没有可匹配的情况下返回DBNull.Value。访问方式与方法调用相同, 之后介绍,可能抛出异常。


    • 写索引化属性

      SetValue时, 如果name参数为string类型, 并且name的值不匹配任何可访问字段名, 属性名, 方法名, 则把name假设为索引参数, 执行后续访问, 这样的访问存在二义性。name参数为其它类型, 则直接认定为索引参数, 遍历所有单参数可写索引属性, 匹配类型进行访问, 没有可匹配的情况下操作被忽略。访问方式与方法调用相同, 之后介绍,可能抛出异常。


  • 7.7.7 事件访问

    常见脚本语言没有相应的语法对应,所以不支持。


  • 7.7.8 方法调用流程

    读方法获得的Invokable接口对象上执行Invoke,构造对象,读索引属性,写索引属性,这4种情况下执行方法调用流程。

    • 参数匹配

      遍历所有同名方法,首先根据形参实参列表长度进行匹配,然后逐参数匹配,获得适合的方法列表。参数匹配使用一个加权评价算法,得分最高的那些方法被选中,评价值为Int32.MinValue的方法被直接抛弃,如果全部都是Int32.MinValue,全部抛弃。方法评价值的计算:

      • 1. 遍历形参列表

        • 1.1. 使用用形参对应位置的实参与形参类型,计算参数评价值

          • 1.1.1. 形参类型等于实参类型,表示完全匹配,返回评价值1

          • 1.1.2. 实参是形参类型的实例,或者实参为null并且形参类型为非值类型,表示兼容的转换,返回评价值0

          • 1.1.3. 如果形参类型为bool或者string,返回评价值-1,这是为了兼容脚本语言特性,可以进行安全的转换。

          • 1.1.4. 如果形参实参都是IConvertible类型,说明还有机会使用System.Convert.ChangeType进行转换,数值类型都是IConvertible,转换损失精度的情况下会抛出异常,这就是接下来参数绑定过程忽略失败转换的主要原因,这种情况下返回评价值-2。

          • 1.1.5. 其它情况不存在有效的转换,返回Int32.MinValue。

        • 1.2. 如果参数评价值为Int32.MinValue,则方法的评价值直接返回Int32.MinValue,否则累加到方法评价值中。

      • 2. 如果所有实参都被匹配过了,返回方法评价值


    • 参数绑定

      遍历匹配后获得的方法列表,执行参数类型转换,忽略失败的转换,记录成功的转换,如果转换成功一次以上,抛出异常System.Reflection.AmbiguousMatchException。遍历形参列表执行参数绑定,实际上就是按照形参类型对实参进行转换,获得新的参数列表对象。转换方法:

      • 1. 实参是形参类型的实例,或者实参为null并且形参类型为非值类型,直接返回该实参,实际上对应了评价值为1,0的两种情况。

      • 2. 形参类型为bool的情况下(评价值-1),按照脚本语言的惯例,转换实参:

        • 2.1. 实参为null或者DBNull.Value,转换为false

        • 2.2. 实参为char类型,等于'\0'为false,否则true

        • 2.3. 实参为string类型,长度为0为false,否则true

        • 2.4. 实参为各种数值类型, 交给System.Convert.ChangeType执行转换, 这些转换不会抛出异常, 可以获得符合脚本语言惯例的结果(当然了, 这不是Lua的惯例, 对Lua而言,if 0 then print('true') else print('false') end 输出true,这一点跟Lua不兼容)。

        • 2.5. 其它情况一律转换为true

      • 3. 形参类型为string的情况下(评价值-1),使用构造ReflectionCache时提供的toString委托,将实参提交给脚本系统进行转换。

      • 4. 其它情况(评价值-2)交给System.Convert.ChangeType执行转换,可能抛出异常,指示形参列表对应的方法不能被调用,应该忽略。


    • 方法调用

      如果存在成功的转换,使用转换结果执行对应的方法;如果没有,对于Invokable对象上的Invoke,抛出异常指示没有合适的方法可用,构造对象的情况下,抛出异常指示没有合适构造函数可用,读索引属性的情况下返回DBNull.Value,写索引属性忽略该操作。Invokable对象上的Invoke的方法如果返回类型为void,返回DBNull.Value;


    • 注意事项

      • 1. 不支持包含可选参数的方法, 也就是说调用可选参数方法时, 可选的那些参数不能省略。可选参数方法并不常用, 支持可选参数的版本性能会将大大降低, 得不偿失。

      • 2. 参数匹配实际上是为了减少参数绑定失败次数,提高性能,而作的预处理。

      • 3. 实现上,首先按照参数长度进行匹配,如果选择出唯一的方法,则省略参数匹配过程,可以提高性能,所以减少重载的使用才是最优的方法。

      • 4. 参数评价算法中, 为了减少参数绑定失败概率, 向string转换的评价值高于数值间转换, 某些情况下可能违反脚本使用习惯。 例如, 定义方法int add(int a, int b);与string add(string a, string b);分别执行数值加法与串连接。脚本语言调用add(1,2), 如果是javascript来调用返回3,如果是Lua来调用,返回字符串12,原因在于,Javascript的整数是int,可以严格匹配,评价值为2和-2;Lua的整数是long,评价值为-4与-2,这类情况需要小心。

      • 5. 参数绑定是个完全动态的过程, 比如脚本语言中使用一个变量作为参数调用方法func(short x);如果变量的值在short范围内, 该方法被调用, 否则报告找不到合适的方法。


    • 正确处理委托

      C#对象进入脚本环境,需要检测该对象是否为委托对象,如果是,则需要进一步在该对象上GetValue获取Invoke方法的Invokable包装作为实际被脚本引用的对象,在脚本环境看来,这样的对象与读取对象方法获得的Invokable包装没有任何区别。Invokable包装重新回到C#就必须进一步判断被包装的对象是否是委托对象,如果是,返回委托对象,否则返回包装本身,这样才能做到进出一致,这也就是Invokable.GetTarget()方法需要完成的功能。

      委托对象必须被包装,原因在于,.Net实现上,提供了一个Delegate基类,delegate关键字定义的信息,被用来生成一个类,该类继承自Delegate,生成的Invoke方法的参数列表正好就是delegate定义的参数列表,有了它才能进行正确的类型转换,然后调用。如果不包装,直接调用Delegate.DynamicInvoke则缺少类型转换过程,不能保证正确性。


上一页 下一页