天天看點

Marshal.SizeOf和sizeof的差別

sizeof在非Unsafe環境下隻能用于預定義的一系列類型,如Int,Short等等。而在Unsafe環境下,sizeof可以被用于值類型,但是值類型中不可以有引用類型,否則C#編譯器會報錯:

error CS0208: Cannot take the address of, get the size of, or declare a pointer to a managed type ('SizeOf.Program.MyStruct')

而Marshal.SizeOf則是獲得該類型被Marshal(轉換,通常翻譯為列集,指資料從一種類型轉換到另外一種類型)到對應的非托管類型的大小。和sizeof不同,Marshal.SizeOf允許用在含有引用類型的值類型上:

1: [StructLayout(LayoutKind.Sequential)]      
2: struct MyStruct      
3: {      
4:     string s;      
5: }      

Marshal.SizeOf(MyStruct)結果為4或者8,因為string被Marshal成char*。

如果用在不含有引用類型的值類型上,其結果也有可能和sizeof完全不一樣,如對于下面的值類型:

1: struct MyStruct      
2: {      
3:     char b;      
4: }      

sizeof(MyStruct)為2,而Marshal.SizeOf(typeof(MyStruct))結果則為1。這是因為在.NET中char總是Unicode,而預設情況下char會被Marshal成8位的Ansi字元,是以結果不同。

反之,如果我們指定這個char被Marshal成short值(也就是UTF16),如下:

1: [StructLayout(LayoutKind.Sequential)]      
2: struct MyStruct      
3: {      
4:     [MarshalAs(UnmanagedType.I2)]      
5:     char b;      
6: }      

那麼sizeof和Marshal.SizeOf結果均為2。MarshalAs這個Attribute可以影響Marshal.SizeOf的結果,而不能影響sizeof的結果。

一個有意思的情況是,如果值類型不含任何成員,如下:

1: struct MyStruct      
2: {      
3: }      

Sizeof和Marshal.SizeOf結果均為1,而不是0。這個結果和C++的結果是一緻的。原因很簡單:如果聲明一個這樣的數組,如果元素大小為0的話,那麼每個元素都具有相同的位址,這是不為C++标準所允許的,和正常的非0的情況也不一緻。.NET在這裡采用和c++相同的規則,也認為空的值類型大小為1。

最後需要注意的是,如果MyStruct是模闆:

1: struct MyStruct<T>      
2: {      
3:     T a;      
4: }      

如果對Marshal.SizeOf傳入MyStruct<>或者MyStruct<int>這樣的類型,則抛出ArgumentException,因為Marshal.SizeOf完全不支援泛型。這個是曆史遺留問題,從本質上來講執行個體化的模闆類型(MyStruct<int>)應該是支援的,據說當時主要是沒有時間加上對模闆的支援。

同樣的,sizeof也不支援模闆類型,而且連MyStruct<int>這樣子的類型也不支援。C#編譯器會對sizeof(MyStruct<int>)報錯:error CS0208: Cannot take the address of, get the size of, or declare a pointer to a managed type ('SizeOf.Program.MyStruct<int>')