盡管在C#中結構與類有着驚人的相似度,但在實際應用中,會常常因為一些特殊之類而錯誤的使用它,下面幾點內容是筆者認為應該注意的:
對於結構
1)可以有方法與屬性
2)是密封的,不能被繼承,或繼承其他結構
3)結構隱式地繼承自System.ValueType
4)結構有默認的無參數構造函數,可以將每個字段初始化為默認值,但這個默認的構造函數不能被替換,即使重載了帶參數的構造函數
5)結構沒有析構函數
6)除了const成員外,結構的字段不能在聲明結構時初始化
7)結構是值類型,在定義時(盡管也使用new運算符)會分配堆棧空間,其值也存儲於堆棧
8)結構主要用於小的數據結構,為了更好的性能,不要使用過於龐大的結構
9)可以像類那樣為結構提供 Close() 或 Dispose() 方法
如果經常做通信方面的程序,結構體是非常有用的(為了更有效地組織數據,建議使用結構體),也會遇到字節數據與結構體相互轉化的問題,下面是一般解決方法:
如何定義一個按字節順序存儲的結構體?
1 [StructLayout(LayoutKind.Sequential, Pack = 1)] //順序排列,並按1字節對齊 (這里保存的是一組飛機參數,共73字節)
2 public struct StructPlane
3 {
4 public byte serialNum;
5 public double pitch;
6 public double roll;
7 public double yaw;
8 public double pitchVel;
9 public double rollVel;
10 public double yawVel;
11 public double alt;
12 public double vz;
13 public ushort pwm1;
14 public ushort pwm2;
15 public ushort pwm3;
16 public ushort pwm4;
17 };
結構體轉字節數組的方法:
注:
1. 一般PC用小端模式(即高位存放於高地址,低位存放於低地址,反之為大端模式)存放數據,如果另一通訊設備用大端存儲數據,則相互轉化時就要注意了。
2. 結構體需按上面的方式,順序排列並按1字節對齊,下面的所有方法都一樣。
1 //需添加的引用,提供Marshal類
2 using System.Runtime.InteropServices;
3
4 /// <summary>
5 /// 結構體轉字節數組(按小端模式)
6 /// </summary>
7 /// <param name="obj">struct type</param>
8 /// <returns></returns>
9 byte[] StructureToByteArray(object obj)
10 {
11 int len = Marshal.SizeOf(obj);
12 byte[] arr = new byte[len];
13 IntPtr ptr = Marshal.AllocHGlobal(len);
14 Marshal.StructureToPtr(obj, ptr, true);
15 Marshal.Copy(ptr, arr, 0, len);
16 Marshal.FreeHGlobal(ptr);
17 return arr;
18 }
19
20 /// <summary>
21 /// 結構體轉字節數組(按大端模式)
22 /// </summary>
23 /// <param name="obj">struct type</param>
24 /// <returns></returns>
25 byte[] StructureToByteArrayEndian(object obj)
26 {
27 object thisBoxed = obj; //copy ,將 struct 裝箱
28 Type test = thisBoxed.GetType();
29
30 int offset = 0;
31 byte[] data = new byte[Marshal.SizeOf(thisBoxed)];
32
33 object fieldValue;
34 TypeCode typeCode;
35 byte[] temp;
36 // 列舉結構體的每個成員,並Reverse
37 foreach (var field in test.GetFields())
38 {
39 fieldValue = field.GetValue(thisBoxed); // Get value
40
41 typeCode = Type.GetTypeCode(fieldValue.GetType()); // get type
42
43 switch (typeCode)
44 {
45 case TypeCode.Single: // float
46 {
47 temp = BitConverter.GetBytes((Single)fieldValue);
48 Array.Reverse(temp);
49 Array.Copy(temp, 0, data, offset, sizeof(Single));
50 break;
51 }
52 case TypeCode.Int32:
53 {
54 temp = BitConverter.GetBytes((Int32)fieldValue);
55 Array.Reverse(temp);
56 Array.Copy(temp, 0, data, offset, sizeof(Int32));
57 break;
58 }
59 case TypeCode.UInt32:
60 {
61 temp = BitConverter.GetBytes((UInt32)fieldValue);
62 Array.Reverse(temp);
63 Array.Copy(temp, 0, data, offset, sizeof(UInt32));
64 break;
65 }
66 case TypeCode.Int16:
67 {
68 temp = BitConverter.GetBytes((Int16)fieldValue);
69 Array.Reverse(temp);
70 Array.Copy(temp, 0, data, offset, sizeof(Int16));
71 break;
72 }
73 case TypeCode.UInt16:
74 {
75 temp = BitConverter.GetBytes((UInt16)fieldValue);
76 Array.Reverse(temp);
77 Array.Copy(temp, 0, data, offset, sizeof(UInt16));
78 break;
79 }
80 case TypeCode.Int64:
81 {
82 temp = BitConverter.GetBytes((Int64)fieldValue);
83 Array.Reverse(temp);
84 Array.Copy(temp, 0, data, offset, sizeof(Int64));
85 break;
86 }
87 case TypeCode.UInt64:
88 {
89 temp = BitConverter.GetBytes((UInt64)fieldValue);
90 Array.Reverse(temp);
91 Array.Copy(temp, 0, data, offset, sizeof(UInt64));
92 break;
93 }
94 case TypeCode.Double:
95 {
96 temp = BitConverter.GetBytes((Double)fieldValue);
97 Array.Reverse(temp);
98 Array.Copy(temp, 0, data, offset, sizeof(Double));
99 break;
100 }
101 case TypeCode.Byte:
102 {
103 data[offset] = (Byte)fieldValue;
104 break;
105 }
106 default:
107 {
108 //System.Diagnostics.Debug.Fail("No conversion provided for this type : " + typeCode.ToString());
109 break;
110 }
111 }; // switch
112 if (typeCode == TypeCode.Object)
113 {
114 int length = ((byte[])fieldValue).Length;
115 Array.Copy(((byte[])fieldValue), 0, data, offset, length);
116 offset += length;
117 }
118 else
119 {
120 offset += Marshal.SizeOf(fieldValue);
121 }
122 } // foreach
123
124 return data;
125 } // Swap
字節數組轉結構體的方法:
/// <summary>
/// 字節數組轉結構體(按小端模式)
/// </summary>
/// <param name="bytearray">字節數組</param>
/// <param name="obj">目標結構體</param>
/// <param name="startoffset">bytearray內的起始位置</param>
public static void ByteArrayToStructure(byte[] bytearray, ref object obj, int startoffset)
{
int len = Marshal.SizeOf(obj);
IntPtr i = Marshal.AllocHGlobal(len);
// 從結構體指針構造結構體
obj = Marshal.PtrToStructure(i, obj.GetType());
try
{
// 將字節數組復制到結構體指針
Marshal.Copy(bytearray, startoffset, i, len);
}
catch (Exception ex) { Console.WriteLine("ByteArrayToStructure FAIL: error " + ex.ToString()); }
obj = Marshal.PtrToStructure(i, obj.GetType());
Marshal.FreeHGlobal(i); //釋放內存,與 AllocHGlobal() 對應
}
/// <summary>
/// 字節數組轉結構體(按大端模式)
/// </summary>
/// <param name="bytearray">字節數組</param>
/// <param name="obj">目標結構體</param>
/// <param name="startoffset">bytearray內的起始位置</param>
public static void ByteArrayToStructureEndian(byte[] bytearray, ref object obj, int startoffset)
{
int len = Marshal.SizeOf(obj);
IntPtr i = Marshal.AllocHGlobal(len);
byte[] temparray = (byte[])bytearray.Clone();
// 從結構體指針構造結構體
obj = Marshal.PtrToStructure(i, obj.GetType());
// 做大端轉換
object thisBoxed = obj;
Type test = thisBoxed.GetType();
int reversestartoffset = startoffset;
// 列舉結構體的每個成員,並Reverse
foreach (var field in test.GetFields())
{
object fieldValue = field.GetValue(thisBoxed); // Get value
TypeCode typeCode = Type.GetTypeCode(fieldValue.GetType()); //Get Type
if (typeCode != TypeCode.Object) //如果為值類型
{
Array.Reverse(temparray, reversestartoffset, Marshal.SizeOf(fieldValue));
reversestartoffset += Marshal.SizeOf(fieldValue);
}
else //如果為引用類型
{
reversestartoffset += ((byte[])fieldValue).Length;
}
}
try
{
//將字節數組復制到結構體指針
Marshal.Copy(temparray, startoffset, i, len);
}
catch (Exception ex) { Console.WriteLine("ByteArrayToStructure FAIL: error " + ex.ToString()); }
obj = Marshal.PtrToStructure(i, obj.GetType());
Marshal.FreeHGlobal(i); //釋放內存
}
使用示例一:
... ...
byte[] packet = new byte[73]{...};
StructPlane structPlane = new StructPlane();
object structType = structPlane;
ByteArrayToStructure(packet, ref structType, 0);
使用示例二:
StructPlane structPlane = new StructPlane();
structPlane.serialNum = ...;
structPlane.time = ...;
structPlane.pitch = ...;
... ...
byte[] datas = StructureToByteArray(structPlane);