记得刚毕业那会,看过一阵CLR via C#,由于书中知识对于我来说过于深奥,最终只得放弃。而今重新拾起此书并结合工作中的一些经验,偶有小感就写成随笔分享给大家,便于共同探讨,也能帮助我成长。
执行程序集的代码
前几天组里有个测试找我帮他们看个自动化测试的用例,该用例从某一天就一直抛出加载程序集失败的异常。在这里我用一个场景去模拟当时的情形 ,有这么一个测试用例:我们测试Visual Studio创建工程(随机创建Silverlight或者WPF),并为新建的工程添加一个按钮。我们需要为WPF和Silverlight创建相应的按钮,但是我们把这两种按钮添加的逻辑放到一个方法里面,并通过一个布尔值去区分不同的工程。添加按钮逻辑的样例代码如下:
1 public void AddButton(bool isSiverlight) 2 { 3 if (isSiverlight) 4 { 5 Silverlight.System.Windows.Controls.Button button = new Silverlight.System.Windows.Controls.Button(); 6 } 7 else 8 { 9 System.Windows.Controls.Button button = new System.Windows.Controls.Button(); 10 } 11 }
产品以前有个功能,当需要用到一些没有加载的程序集时,产品根据一些逻辑找到该程序集但是并不管该程序集是否运行的工程有关。但是现在改成了只会查找和加载和当前工程相关的程序集。也就是说对于WPF的工程来说,产品不会再去查找或者加载Silverlight的程序集。因此现在在WPF工程中执行AddButton方法时,会因找不到Silverlight程序集抛出“Could not load file or assembly 'System.Windows, Version=5.0.5.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e' or one of its dependencies”(注:此程序集不在GAC中,也不是Copy Local的)。找到了原因之后,我将AddButton的逻辑稍作改动如下:
1 public void AddButtonUsingSeperateMethod(bool isSiverlight) 2 { 3 if (isSiverlight) 4 { 5 this.AddSiverlightButton(); 6 } 7 else 8 { 9 this.AddWpfButton(); 10 } 11 } 12 13 private void AddSiverlightButton() 14 { 15 Silverlight.System.Windows.Controls.Button button = new Silverlight.System.Windows.Controls.Button(); 16 } 17 18 private void AddWpfButton() 19 { 20 System.Windows.Controls.Button button = new System.Windows.Controls.Button(); 21 }
我将AddButton里的方法抽成了两个独立的方法,该用例便正常了。测试觉得很奇怪,问我为什么写成两个独立的方法就可以,而独立的方法便会有异常,逻辑明明是一样的啊。我于是把CLR via C#找出来,找到“执行程序集代码”的章节跟他解释了一番,他便明白了其中的原因。
CLR第一次执行到某个方法时,JIT编译器会将该方法的IL转换成本地CPU指令(步骤参见下图,来自CLR via C#)。因此当执行AddButton方法时,会将里面的IL代码全部解释为CPU指令,当转换"Silverlight.System.Windows.Controls.Button button = new Silverlight.System.Windows.Controls.Button();"时,会需要加载定义该类的程序集查找该类的信息,由于找不到该程序集便有异常发生了。但是AddButtonUsingSeperateMethod就不一样了,由于此时是WPF工程,JIT在转换AddButtonUsingSeperateMethod时并不立即将AddSiverlightButton这个方法的具体实现也一并转换成CPU指令,因而也就不需要加载Silverlight的程序集,从而避免了异常的发生。
拆箱
“拆箱的代价比装箱低得多,拆箱其实就是获取一个指针的过程,该指针指向包含在一个对象中的原始值类型(数据字段),拆箱不要求在内存中复制任何字段”
下面的代码将struct类型p装箱成object并赋值给o,随后又将o拆箱成Point并赋值给p。通常我们所指的拆箱便是(Point)o这个表达式,其实这个表达式包含了拆箱和赋值两个操作。
1 public struct Point 2 { 3 public int X; 4 public int Y; 5 } 6 7 class Program 8 { 9 static void Main(string[] args) 10 { 11 Point p = new Point() { X = 1, Y = 1 }; 12 object o = p; 13 p = (Point)o; 14 } 15 }
单纯地就拆箱来讲只是获取一个指针,但随后会紧接着一个赋值的动作。那么这个值是赋到哪里呢?我们来尝试着将拆箱后的o中的X的值直接改成2,编译的时候我们会得到一个“Cannot modify the result of an unboxing conversion”,从MSDN可以得知,这是CS0445的编译错误,从该错误的MSDN解释可得知,我们是将拆箱的值赋值到内存中的临时变量中的。因此要记住,我们是不能直接修改拆箱结果的值的,必须先将其赋值程序中事先定义好的变量中。
1 ((Point)o).X = 2;
由于个人能力所限,文中解释不免有不足和错误之处,望各位读者不吝指正,供大家共同学习进步,也便于以后分享更好的文章。