大家都知道AssemblyVersionAttribute是用来指定Assembly的版本号使用的,但是不知道你有没有考虑过这个问题:这个Attribute真的生成到了最后的Assembly中吗?
我们建立一个简单的C#项目试一下便可以知道。在新建的C#项目中AssemblyInfo.cs缺省有如下的内容:
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle(/"TestAssemblyVersionAttribute/")]
[assembly: AssemblyDescription(/"/")]
[assembly: AssemblyConfiguration(/"/")]
[assembly: AssemblyCompany(/"MSIT/")]
[assembly: AssemblyProduct(/"TestAssemblyVersionAttribute/")]
[assembly: AssemblyCopyright(/"Copyright © MSIT 2008/")]
[assembly: AssemblyTrademark(/"/")]
[assembly: AssemblyCulture(/"/")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid(/"a39eea23-7bc8-47c2-aca8-93136279a0ec/")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the /'*/' as shown below:
// [assembly: AssemblyVersion(/"1.0.*/")]
[assembly: AssemblyVersion(/"1.0.0.0/")]
[assembly: AssemblyFileVersion(/"1.0.0.0/")]
Build一下,生成的文件用ILDASM查看,大部分Attribute都可以在Manifest中找到:
// Metadata version: v2.0.50727
.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z//V.4..
.ver 2:0:0:0
}
.assembly TestAssemblyVersionAttribute
{
.custom instance void [mscorlib]System.Reflection.AssemblyTitleAttribute::.ctor(string) = ( 01 00 1C 54 65 73 74 41 73 73 65 6D 62 6C 79 56 // ...TestAssemblyV
65 72 73 69 6F 6E 41 74 74 72 69 62 75 74 65 00 // ersionAttribute.[Page]
00 )
.custom instance void [mscorlib]System.Reflection.AssemblyDescriptionAttribute::.ctor(string) = ( 01 00 00 00 00 )
.custom instance void [mscorlib]System.Reflection.AssemblyConfigurationAttribute::.ctor(string) = ( 01 00 00 00 00 )
.custom instance void [mscorlib]System.Reflection.AssemblyCompanyAttribute::.ctor(string) = ( 01 00 04 4D 53 49 54 00 00 ) // ...MSIT..
.custom instance void [mscorlib]System.Reflection.AssemblyProductAttribute::.ctor(string) = ( 01 00 1C 54 65 73 74 41 73 73 65 6D 62 6C 79 56 // ...TestAssemblyV
65 72 73 69 6F 6E 41 74 74 72 69 62 75 74 65 00 // ersionAttribute.
00 )
.custom instance void [mscorlib]System.Reflection.AssemblyCopyrightAttribute::.ctor(string) = ( 01 00 16 43 6F 70 79 72 69 67 68 74 20 C2 A9 20 // ...Copyright .. [Page]
4D 53 49 54 20 32 30 30 38 00 00 ) // MSIT 2008..
.custom instance void [mscorlib]System.Reflection.AssemblyTrademarkAttribute::.ctor(string) = ( 01 00 00 00 00 )
.custom instance void [mscorlib]System.Runtime.InteropServices.ComVisibleAttribute::.ctor(bool) = ( 01 00 00 00 00 )
.custom instance void [mscorlib]System.Runtime.InteropServices.GuidAttribute::.ctor(string) = ( 01 00 24 61 33 39 65 65 61 32 33 2D 37 62 63 38 // ..$a39eea23-7bc8
2D 34 37 63 32 2D 61 63 61 38 2D 39 33 31 33 36 // -47c2-aca8-93136
32 37 39 61 30 65 63 00 00 ) // 279a0ec..[Page]
.custom instance void [mscorlib]System.Reflection.AssemblyFileVersionAttribute::.ctor(string) = ( 01 00 07 31 2E 30 2E 30 2E 30 00 00 ) // ...1.0.0.0..
// --- The following custom attribute is added automatically, do not uncomment -------
// .custom instance void [mscorlib]System.Diagnostics.DebuggableAttribute::.ctor(valuetype [mscorlib]System.Diagnostics.DebuggableAttribute/DebuggingModes) = ( 01 00 07 01 00 00 00 00 )
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 )
.custom instance void [mscorlib]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78 // ....T..WrapNonEx
63 65 70 74 69 6F 6E 54 68 72 6F 77 73 01 ) // ceptionThrows.
.hash algorithm 0x00008004
.ver 1:0:0:0
}
.module TestAssemblyVersionAttribute.exe
// MVID: {383D6DF2-7A1D-41BA-944D-26979117014E}
.imagebase 0x00400000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003 // WINDOWS_CUI
.corflags 0x00000001 // ILONLY
// Image base: 0x00350000
只有一个例外,也就是AssemblyVersionAttribute在这个Manifest中是找不到的。而真正的指定Version的语句在这里:
.ver 1:0:0:0
我们再用另外一种方式,仿照C#编译器来动态生成一个Assembly来试一下:
AssemblyName name = new AssemblyName();
name.Name = /"Result/";
name.Version = new Version(/"1.1/");[Page]
AssemblyBuilder asmBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(name, AssemblyBuilderAccess.ReflectionOnly);
ConstructorInfo asmVersionCtor = typeof(AssemblyVersionAttribute).GetConstructor(new Type[] { typeof(string) });
CustomAttributeBuilder attrBuilder = new CustomAttributeBuilder(asmVersionCtor, new object[] { /"2.2/" });
asmBuilder.SetCustomAttribute(attrBuilder);
asmBuilder.Save(/"Result.DLL/");
这里我们指定Assembly版本号为1.1,而AssemblyVersionAttribute则是2.2,那么最终的结果如何呢?
// Metadata version: v2.0.50727
.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z//V.4..
.ver 2:0:0:0
}
.assembly Result
{
.custom instance void [mscorlib]System.Reflection.AssemblyVersionAttribute::.ctor(string) = ( 01 00 03 32 2E 32 00 00 ) // ...2.2..
.hash algorithm 0x00008004
.ver 1:1:0:0
}
.module RefEmit_OnDiskManifestModule
// MVID: {AF35088A-F419-4F88-A011-D29FF41C1E20}
.imagebase 0x00400000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003 // WINDOWS_CUI
.corflags 0x00000001 // ILONLY
// Image base: 0x00880000
可以看到,首先AssemblyVersionAttribute并没有决定Assembly的版本号,而AssemblyVersionAttribute本身却被放到了Assembly的Manifest里面,反而和c#编译器的结果不一样了。
事实上,AssemblyVersionAttribute对于CLR本身并没有意义,并不能指定Assembly的版本号。AssemblyVersionAttribute本身只是对于编译器比如c#或者VB.NET等才有意义,编译器根据Attribute的内容生成对应版本号的文件,这个Attribute的内容本身并没有最后进入结果的Assembly。
除此之外,还有一些Attribute本身并不是CustomAttribute,而是作为Metadata中的特殊信息存在,可以是Bit Flag,也可以是Signature的一部分。典型的例子是和COM Interop有关的Attribute,如MarshalAsAttribute,DllImportAttribute,StructLayoutAttribute,比如:[Page]
[DllImport(/"kernel32.dll/", EntryPoint=/"Beep/", PreserveSig=true, SetLastError=true)]
[return:MarshalAs(UnmanagedType.Bool)]
public static extern bool MyBeep(uint freq, uint duration);
实际上对应的则是:
.method public hidebysig static pinvokeimpl(/"kernel32.dll/" as /"Beep/" lasterr winapi)
bool marshal( bool) MyBeep(uint32 freq,
uint32 duration) cil managed preservesig
{
}
其中pinvokeimpl对应DllImportAttribute,”kernel32.dll” as “Beep”很明显对应DLL名字和EntryPoint,lasterr则是SetLastError=true,marshal(bool)对应MarshalAs(bool),preservesig对应PreserveSig=true。其中,有些信息是整个函数的Signature的一部分,有些则是BitFlag,CLR在运行的时候动态解析这些信息速度会更快,比CustomAttribute要快很多,原因很简单:我们随便找一个CustomAttribute来看:
.assembly Result
{
.custom instance void [mscorlib]System.Reflection.AssemblyVersionAttribute::.ctor(string) = ( 01 00 03 32 2E 32 00 00 ) // ...2.2..
.hash algorithm 0x00008004
.ver 1:1:0:0
}
看看上面的AssemblyVersionAttribute,内容为2.2,这个2.2是怎么读出来的呢?.ctor(string)表示构造Attribute的实例的时候要调用参数是String的构造函数,而后面的那一串数字,则是根据CLI所定义的规则指定的构造函数的参数:第一个01 00是16位长的标识,总是不变,03是字符串长度,后面的32 2E 32是字符串的UTF8编码,也就是2.2(这里只讲到字符串,对于其他不同类型的参数解释方式又不一样),00 00是16位长的0,表示后面跟0个可选命名参数(比如上面提到的PreserveSig=true这样的形式)。换句话说,CustomAttribute是需要CLR在运行时候动态调用构造函数,并分析这一串二进制数值得出参数来创建一个新的CustomAttribute的实例,比动态解析Metadata要慢。当然了,只有CLR自己支持的特殊Attribute才有这种待遇了,一般我们写的Attribute还是要老老实实的这么构造的。这些特殊的Attribute,用GetCustomAttributes函数是无法得到的,而只能用特殊方法获得。比如PreserveSig只能用GetMethodImplementationFlags函数获得。