1. 程式人生 > 其它 >C# 建立 union 結構

C# 建立 union 結構

使用C#建立聯合結構體

問題

想要用C#建立一種資料型別,類似於 C/C++ 中的聯合(union)型別。聯合型別主要用於互操作場景,其中非託管程式碼接受或返回一個聯合型別。

解決辦法

使用一個結構,並用 [StructLayout] 特性修飾它(在建構函式中指定 LayoutKind.Explicit 佈局型別)。此外,利用 FieldOffset 特性標記結構中的每個欄位。下面的結構定義了一個聯合型別,其中可以儲存一個帶符號數值。

using System.Runtime.InteropServices;
[StructLayoutAttribute(LayoutKind.Explicit)]
struct SignedNumber 
{
	[FieldOffsetAttribute(0)] public sbyte Num1;
	[FieldOffsetAttribute(0)] public short Num2;
	[FieldOffsetAttribute(0)] public int Num3;
	[FieldOffsetAttribute(0)] public long Num4;
	[FieldOffsetAttribute(0)] public float Num5;
	[FieldOffsetAttribute(0)] public double Num6;
}

下一個結構類似於 SignedNumber 結構,不同之處是除了帶符號的數值之外,它還可以包含 String 型別。

[StructLayoutAttribute(LayoutKind.Explicit)]
struct SignedNumberWithText 
{
	[FieldOffsetAttribute(0)] public sbyte Num1;
	[FieldOffsetAttribute(0)] public short Num2;
	[FieldOffsetAttribute(0)] public int Num3;
	[FieldOffsetAttribute(0)] public long Num4;
	[FieldOffsetAttribute(0)] public float Num5;
	[FieldOffsetAttribute(0)] public double Num6;
	[FieldOffsetAttribute(16)] public string Text1;
}

若非必須,不建議使用這種類似於union型別的結構體。

講解

聯合型別是一種在 C++ 程式碼中較為常見的結構型別;不過,有一種方式可以使用 C# 中的結構資料型別來複制其結構。聯合 (union)是一種結構,在記憶體中的特定位置為該結構接受多種型別。例如,SignedNumber 結構是使用 C# 結構建立的一個聯合型別的結構。這種結構可以接受任何型別的帶符號的數值型別(sbyte 、int 和 long 等),但它只在結構中的同一個位置(同一偏移量)接受這種數字型別。
 由於 StructLayoutAttribute 可以同時應用於結構和類,在建立聯合資料型別時也可以使用類。
注意 FieldOffsetAttribute 將值 0 傳遞給它的建構函式。這表明這個欄位距離結構開始處的偏移量為 0 位元組。可以將這個特性與 StructLayoutAttribute 結合使用,手動強制指定結構中的欄位開始於什麼位置(即每個欄位在記憶體中相對於這個結構開始處的偏移量)。FieldOffsetAttribute 只能與設定為 LayoutKind.Explicit 的 StructLayoutAttribute 一起使用。此外,它不能用於結構內的靜態成員。
聯合型別可能會帶來一些問題,因為幾種型別實質上是相互疊加在一起的。最大的問題是如何從聯合型別結構中提取正確的資料型別。思考一下,如果你選擇在 SignedNumber 結構中儲存 long 數值型別的值 long.MaxValue ,會發生什麼情況。隨後,你可能會偶然嘗試從這個結構中提取一個 byte 資料型別值。這樣操作,你將會只取回這個 long 值中的第一位元組。
另一個問題是在正確的偏移位置開始欄位。SignedNumberWithText 聯合型別在偏移量為 0 的位置疊加了大量帶符號的數值資料型別。這個結構中的最後一個欄位位於記憶體中距離這個結構開始處偏移量為 16 位元組的位置。如果你意外地把字串欄位 Text1 覆蓋在任何其他帶符號的數值資料型別之上,在執行時將得到一個異常。基本規則是:允許你把一種值型別疊加在另一種值型別之上,但是不能把一種引用型別疊加於一種值型別之上。如果用以下特性標記 Text1 欄位:
[FieldOffsetAttribute(14)]
就會在執行時引發下面這個異常(注意,編譯器不會捕獲這個問題)。
System.TypeLoadException: 不能從程式集載入型別'SignedNumberWithText', Version=1.0.0.0, Culture=neutral, PublicKeyToken=fe85c3941fbcc4c5' because it contains an object field at offset 14 that is incorrectly aligned or overlapped by a non-object field.
注意:在 C# 中使用複雜的聯合型別時,一定要保證正確的偏移量。

參考資料: