这一节只是相关于ILASM的早期版本(1.0和1.1),但是我仍然认为这是有用的信息。考虑一下这些.NET Framework版本的基本大小,你将会不止一次的遇到与这些ILASM的老版本打交道的机会。
如果你安装了.NET Framework的早期版本,你可以对这个代码示例执行一些试验。在任意文本编辑器中打开这个源文件Simple.il并作出如下修改:将对值类型CharArray8的声明移动到字段Format的前面:
}
// End of namespace
.class public explicit CharArray8
extends [mscorlib]System.ValueType { .size 8 }
.field public static valuetype CharArray8 Format at FormatData
事情看起来是井然有序的。但是当你试图编译这个文件时,ILAsm编译器将会失败并报出Unresolved MemberRef ‘Format’的错误信息。
现在再次修改源文件,这次将对值类型CharArray8的声明移动到命名空间Odd.Or的前面:
extends [mscorlib]System.ValueType { .size 8 }
.namespace Odd.or {
.class public auto ansi Even extends [mscorlib]System.Object {
.field public static int32 val
.method public staticvoid check( ) cil managed {
ldsflda valuetype CharArray8 Format
} // End of method
} // End of class
} // End of namespace
.field public static valuetype CharArray8 Format at FormatData
现在,当你保存这段源代码并试图重编译它的时候,这就都回复正常了。这里面发生了什么呢?
在第一次源代码改变后,当Format字段被check方法中的ldsflda指令引用时,值类型CharArray8还没有被声明,它的TypeRef就会因此被忽略,字段引用的签名就会收到该TypeRef作为它的类型。
接着,值类型CharArray8被声明,一个新的TypeDef被创建。然后,当Format字段确实被声明时,它的类型被当成一个局部声明的值类型,而且该字段的签名定义接收到TypeDef作为它的类型。但是,不存在以TypeRef作为类型的名为Format的字段,被声明在模块中的任何地方。从而,你得到了引用到定义的决定性失败。
(有一段时间批判ILAsm编译器在编程级别上缺少匹配签名的能力,伴随着类型分析和通过完全名称和决定范围来匹配TypeRef到TypeDef。然而,请耐心一些。)
第二次改变源代码后,值类型CharArray8首次被声明——以便于所有的引用指向它,而不管这些引用在哪里出现,我们称这个类型为TypeDef。这是一个相当明显的解决方案。
这个解决方案也会变得不明显——在你考虑两个类的时候,它们的成员互相使用对方的类型作为成员。那么先声明哪一个呢?准确地说,全部。
在“类定义”章节我提到了类的修正技术,基于ILAsm,允许你重新打开类的范围从而声明更多类的特性和成员。解决这个声明/引用问题的基本方案是,首先为所有的类指定一个空范围类的定义。据此,你可以完全指定所有的类,包括它们的特性和成员,作为修正。“首轮”类的声明应该携带所有类标记、extends子句和implements子句,还应包括所有内嵌类(还有空范围)。你应该将所有这些成员声明维持到后期。
这种类的预先声明的技术防止了声明/引用错误,而副作用是降低了元数据的大小,因为为了局部化定义的类而忽略多余的TypeRef是不必要的。
(上述对ILAsm的评论,相应的解答是,编译器确实以最可能快的速度进行签名,而不需要更多高级并缓慢的方法,只要你使用到类的预先声明。)
类的预先声明的需求,在ILAsm 2.0版本编译器中被消除了。