.Net RuntimeExplorer开发日志(七) IL to C# - 操作符重载与三目运算符

  操作符与优先级是密不可分的,想想小学时的四则混合运算,如果想改变从左到右的优先级就需要加上括号,这一点实现起来并不难。在定义符号时预先定义其优先级,在符号拼接操作数时就可以与操作数的优先级(最后一个符号的优先级)相比较,如果需要则先拼接括号再拼接符号。

  然而语法糖让操作符变得复杂了。+=、-=等等这些用在数字类型上还好,但如果是通过重载操作符而实现的非基类型运算呢?最容易混乱的是重载二元操作符方法的参数是右向左出栈结果保存在右,而数学运算是左向右拼加结果保存在左。我使用C#这么多年将近十年了,重载操作符这个东西从来都没出现在我的代码中,不过我仍觉得对于一些与数学有关的算法会用到它,所以还是加把力吧。

  首先要弄清这东西的特性,在我的肉眼观察来看,它就是个方法,还必须是静态的公共的,名字是以"op_"加上特殊的英文名称,并且在方法Flags中有SpecialName。所以判断一个方法是不是重载操作符相当容易。其次是对可重载的操作符分类,一元二元要分别处理。先说一元操作符,有五个++、--、+、-、~,对应的方法名称为Increment、Decrement、UnaryPlus、UnaryNegation、OnesComplement,处理上也就是在操作数前贴上符号。二元操作符有14个,加减乘除、求模、等于、不等于、大于、小于、大等于、小等于、与、或、非。其处理方法是在两个操作数中间用符号连接。由于是SpecialName方法,所以这19个操作符与特殊方法的处理合并在一起了,先是通过方法确定操作符,再将方法参数与符号保存,等到输出时再通过rewrite正确拼接。最后,这里我十分不理解UnaryPlus这个一元操作符,它存在于.net代码中,但并没有什么实际用。

  重点要说三元操作符,对于IL译C#来说,还是需要切分处理,把这个表达式分割成三块,比较、值一与值二。原本我以为很简单,后来发现又要改框架了,因为三目运算符并不只有赋值的功能,他还可以是方法的参数,可以包含在条件判断语句中,还可以再嵌套个三目在里面,而在我的解析框架中表达式是最基本单位了,也并非不能在表达式中再切分解析,但那样违反了代码的结构化。我大概用代码示例了一下几种复杂的情况,三目运算符解析的难度可以和switch比肩了,还发现三目运算符有时并非是一个表达式,这同样违反了我当初解析的零栈原则,即:一表达式为一语句。这恰恰也证明了我的肤浅,说多都是泪,生的孩子再丑也要养下去,这个延用这久的框架可改之,不可废之。

  解决方法是在所有IL进行切分表达式之前优先处理三目运算。我的解析方法是基于零栈,但三目的最终结果是有个值在栈中,所以要找到它标志的非空栈br语句,以此句为两个值的分界,再通过遍历找到跳转到两个值的condition语句,继续向上解析其它condition语句,从而将其包装成TernayCode统一为一句IL,以防在后续的解析中被拆分。

  前些天有个朋友问我解析的性能如何,这有点让我为难,因为与广大靠此行业吃饭的人相比,我从来没考虑过自己写的代码的性能。尤其是在三目运算符这里,性能是很差的,做了个简单测试,这二百行代码虽只是沧海一粟,竟然增加了8%的解析时间。不过就目前而言,性能问题至少要等到在解析上全都没有问题以后再做优化。

posted @ 2020-11-18 19:11  ccddnet  阅读(163)  评论(0)    收藏  举报