C#联合Union的实现⽅式
⼀.基础篇
C#不像C++,他本⾝是没有联合Union的,但是可以通过⼿动控制结构体每个元素的位置来实现,这需要结合使
⽤StructLayoutAttribute、LayoutKind以及FieldOffsetAttribute。使⽤它们的时候必须引⽤System.Runtime.InteropServices下⾯是我写的模拟U的联合。
[StructLayout(LayoutKind.Explicit, Size = 4)]
struct U
{
[FieldOffset(0)]
public byte b0;
[FieldOffset(1)]
public byte b1;
[FieldOffset(2)]
public byte b2;
[FieldOffset(3)]
public byte b3;
[FieldOffset(0)]
public int i;
[FieldOffset(0)]
public float f;
}
我们知道联合中每个数据成员都在相同的内存地址开始,所以我们要通过[FieldOffset(0)]应⽤到U的每⼀
个成员,意思就是让这些成员处于同⼀个开始位置。当然,我们得事先告诉.NET这些成员的内存布局由我们来作主,所以要使⽤LayoutKind.Explicit枚举然后传递给StructLayoutAttribute,并应⽤到U上,这样.Net就不会再⼲涉该struct的成员在内存中的布局了。并且我定义了U的Size为12,当然你也可以不定义U的Size。
⽽且使⽤联合进⾏数据转换⽐BitConverter要快。测试⽤例如下:
{
DateTime past = DateTime.Now;
int length = 500000 * 3 * 3;
for (int i = 0; i < length; i++)
{
U a = new U();
a.b0 = 0xFF;
a.b1 = 0xFF;
int res = a.i;
}
DateTime now = DateTime.Now;
Console.WriteLine((now - past));
}
{
DateTime past = DateTime.Now;
int length = 500000 * 3 * 3;
for (int i = 0; i < length; i++)
{
byte[] a = { 0xFF, 0x0F, 0x0F, 0 };
object b = a;
int res = BitConverter.ToInt32(a, 0);
}
DateTime now = DateTime.Now;
union是什么类型Console.WriteLine((now - past));
}
⼆.进阶篇
之前的⽅法还存在好多问题,⽐如数组没法放⼊联合中,会提⽰值和引⽤冲突什么的。
今天⼜研究了⼀下,利⽤C#中可以使⽤指针的特性,结合unsafe和fixed,实现数组类型和普通值类型的共存。
⽅法①数组类型和普通值类型的共存——固定⼤⼩的缓冲区
利⽤固定⼤⼩的缓冲区(fixed)实现数组类型和普通值类型的共存
[StructLayoutAttribute(LayoutKind.Explicit, Pack = 1)]
public unsafe struct A
{
[FieldOffset(0)]
public int a;
[FieldOffset(0)]
public byte b;
[FieldOffset(0)]
public float c;
[FieldOffset(0)]
public fixed byte arr[9];
};
⽅法②结构体转字节数组——1).使⽤联合 2).使⽤指针强制转换
1).使⽤联合,利⽤⼀个和原结构体等长的fixed byte buff[n],这个buff就是我们要的直接数组,访问时需要通过fixed (byte* ta = a.buff) {}来访问。
[StructLayoutAttribute(LayoutKind.Explicit, Pack = 1)]
public unsafe struct A
{
[FieldOffset(0)]
public int a;
[FieldOffset(4)]
public byte b;
[FieldOffset(5)]
public float c;
[FieldOffset(0)]
public fixed byte buff[9];
};
2).直接使⽤指针强制转换,通过fixed,先将结构体转换为void *,再将其转化为byte* b。
fixed (void * ta = &a)
{
byte* b = (byte*)ta ;
}
3).最后通过IntPtr拷贝到C#标准的byte[]中。
byte[] Dbuff = new byte[9];
IntPtr pstart = new IntPtr(a);
Marshal.Copy(pstart, Dbuff, 0, 9);