欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C#如何從byte[]中直接讀取Structure實例詳解

 更新時間:2019年03月01日 11:43:45   作者:小柊  
這篇文章主要給大家介紹了關(guān)于利用C#如何從byte[]里直接讀取Structure的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧

序、前言

emmmmm,首先這篇文章講的不是用BinaryFormatter來進行結(jié)構(gòu)體的二進制轉(zhuǎn)換,說真的BinaryFormatter這個類其實現(xiàn)在的作用并不是特別大了,因為BinaryFormatter二進制序列化出來的結(jié)果只能用于.net平臺,現(xiàn)在可能就用于如存入Redis這種情況下會在使用。

去年年尾的樣子,我閱讀學(xué)習(xí)某C++開發(fā)的程序源碼時,發(fā)現(xiàn)作者用了一個很騷的操作直接將byte[]數(shù)組轉(zhuǎn)為了結(jié)構(gòu)體對象:

上面的data變量是一個指向unsigned char類型的指針,就只要一個簡單的類型轉(zhuǎn)換就可以將一堆unsigned char轉(zhuǎn)換成想要的結(jié)構(gòu)體,這著實有點讓筆者有點羨慕。

后來,筆者想用C#開發(fā)一個流量分析程序,由于需要對IP報文進行仔細(xì)的特征提取,所以不能直接使用第三方數(shù)據(jù)包解析庫(如:PacketDotNet)直接解析,會丟失部分特征,然而使用BinaryReader進行報文頭解析的話,整個解析代碼會寫的喪心病狂的惡(e)心(xin),正在苦惱的時候,突然想起上面提到的那個騷操作時,筆者突然冒出了一個想法,C#里也支持結(jié)構(gòu)體,那我能不能也像C++這樣直接從字節(jié)序列中讀取出結(jié)構(gòu)體呢?

注:本文所有代碼在.net Standard 2.0上測試通過。

一、先聲明,后調(diào)用~

那么在開始前,我們先定義一下要用到的IPv4報文頭結(jié)構(gòu)體,各位同學(xué)IPv4報文頭結(jié)構(gòu)有沒有忘掉啊,如果忘了的話記得先去補補TCP網(wǎng)絡(luò)基礎(chǔ)哈~

因為IPv4頭是允許可變長度的,所以我們的結(jié)構(gòu)體只需要解析到目的地址就夠了,后面的可變選項部分在報文頭前16字節(jié)解析完成之前是不知道會有多長的。

IPv4頭部結(jié)構(gòu)體定義如下,由于IPv4頭部定義中,各個字段并不是都8位整長的,所以有幾個字段是相互并在一起的:

public struct IPv4Header
{
 /// <summary>
 /// IP協(xié)議版本及頭部長度
 /// </summary>
 private byte _verHlen;

 /// <summary>
 /// 差異化服務(wù)及顯式擁塞通告
 /// </summary>
 private byte _dscpEcn;

 /// <summary>
 /// 報文全長
 /// </summary>
 private ushort _totalLength;

 /// <summary>
 /// 標(biāo)識符
 /// </summary>
 private ushort _identification;

 /// <summary>
 /// 標(biāo)志位及分片偏移
 /// </summary>
 private ushort _flagsOffset;

 /// <summary>
 /// 存活時間
 /// </summary>
 private byte _ttl;

 /// <summary>
 /// 協(xié)議
 /// </summary>
 private byte _protocol;

 /// <summary>
 /// 頭部檢驗和
 /// </summary>
 private ushort _checksum;

 /// <summary>
 /// 源地址
 /// </summary>
 private int _srcAddr;

 /// <summary>
 /// 目標(biāo)地址
 /// </summary>
 private int _dstAddr;
}

當(dāng)然,為了方便后續(xù)的使用,還可以在此技術(shù)上設(shè)置一些可讀屬性:

public struct IPv4Header
{
 /// <summary>
 /// IP協(xié)議版本及頭部長度
 /// </summary>
 private byte _verHlen;

 /// <summary>
 /// 差異化服務(wù)及顯式擁塞通告
 /// </summary>
 private byte _dscpEcn;

 /// <summary>
 /// 報文全長
 /// </summary>
 private ushort _totalLength;

 /// <summary>
 /// 標(biāo)識符
 /// </summary>
 private ushort _identification;

 /// <summary>
 /// 標(biāo)志位及分片偏移
 /// </summary>
 private ushort _flagsOffset;

 /// <summary>
 /// 存活時間
 /// </summary>
 private byte _ttl;

 /// <summary>
 /// 協(xié)議
 /// </summary>
 private byte _protocol;

 /// <summary>
 /// 頭部檢驗和
 /// </summary>
 private ushort _checksum;

 /// <summary>
 /// 源地址
 /// </summary>
 private int _srcAddr;

 /// <summary>
 /// 目標(biāo)地址
 /// </summary>
 private int _dstAddr;

 /// <summary>
 /// IP協(xié)議版本
 /// </summary>
 public int Version
 {
 get
 {
  return (this._verHlen & 0xF0) >> 4;
 }
 }

 /// <summary>
 /// 頭部長度
 /// </summary>
 public int HeaderLength
 {
 get
 {
  return this._verHlen & 0x0F;
 }
 }

 /// <summary>
 /// 差異化服務(wù)
 /// </summary>
 public int DSCP
 {
 get
 {
  return (this._dscpEcn & 0xFC) >> 2;
 }
 }

 /// <summary>
 /// 顯式擁塞通告
 /// </summary>
 public int ECN
 {
 get
 {
  return this._dscpEcn & 0x03;
 }
 }

 /// <summary>
 /// 報文全長
 /// </summary>
 public ushort TotalLength
 {
 get
 {
  return this._totalLength;
 }
 }

 /// <summary>
 /// 標(biāo)識符
 /// </summary>
 public ushort Identification
 {
 get
 {
  return this._identification;
 }
 }

 /// <summary>
 /// 保留字段
 /// </summary>
 public int Reserved
 {
 get
 {
  return (this._flagsOffset & 0x80) >> 7;
 }
 }

 /// <summary>
 /// 禁止分片標(biāo)志位
 /// </summary>
 public bool DF
 {
 get
 {
  return (this._flagsOffset & 0x40) == 1;
 }
 }

 /// <summary>
 /// 更多分片標(biāo)志位
 /// </summary>
 public bool MF
 {
 get
 {
  return (this._flagsOffset & 0x20) == 1;
 }
 }

 /// <summary>
 /// 分片偏移
 /// </summary>
 public int FragmentOffset
 {
 get
 {
  return this._flagsOffset & 0x1F;
 }
 }

 /// <summary>
 /// 存活時間
 /// </summary>
 public byte TTL
 {
 get
 {
  return this._ttl;
 }
 }

 /// <summary>
 /// 協(xié)議
 /// </summary>
 public byte Protocol
 {
 get
 {
  return this._protocol;
 }
 }

 /// <summary>
 /// 頭部檢驗和
 /// </summary>
 public ushort HeaderChecksum
 {
 get
 {
  return this._checksum;
 }
 }

 /// <summary>
 /// 源地址
 /// </summary>
 public IPAddress SrcAddr
 {
 get
 {
  return new IPAddress(BitConverter.GetBytes(this._srcAddr));
 }
 }

 /// <summary>
 /// 目的地址
 /// </summary>
 public IPAddress DstAddr
 {
 get
 {
  return new IPAddress(BitConverter.GetBytes(this._dstAddr));
 }
 }
}

二、byte[]轉(zhuǎn)Structure第一版

首先筆者先看了一圈文檔,看看C#有沒有什么方法支持將byte[]轉(zhuǎn)為結(jié)構(gòu)體,逛了一圈發(fā)現(xiàn)一個有這么一個函數(shù):

System.Runtime.InteropServices.Marshal.PtrToStructure<T>(IntPtr)

這個方法接收兩個參數(shù),一個結(jié)構(gòu)體泛型和一個指向結(jié)構(gòu)體數(shù)據(jù)的安全指針(IntPtr),然后這個方法就能返回一個結(jié)構(gòu)體實例出來了。

那么現(xiàn)在的問題就是該如何取得一個byte[]對象的安全指針呢?這里筆者第一反應(yīng)是利用System.Runtime.InteropServices.Marshal.AllocHGlobal方法分配一塊堆外內(nèi)存出來,然后將待轉(zhuǎn)換的byte[]對象復(fù)制到這塊堆外內(nèi)存中,接著利用PtrToStructure<T>函數(shù)將byte[]對象轉(zhuǎn)換成我們想要的結(jié)構(gòu)體對象實例,最后釋放掉堆外內(nèi)存就可以了。

將上面的步驟轉(zhuǎn)換為C#代碼,就形成了第一版的BytesToStructure<T>函數(shù):

/// <summary>
/// 將 byte[] 轉(zhuǎn)為指定結(jié)構(gòu)體實例
/// </summary>
/// <typeparam name="T">目標(biāo)結(jié)構(gòu)體類型</typeparam>
/// <param name="bytes">待轉(zhuǎn)換 byte[]</param>
/// <returns>轉(zhuǎn)換后的結(jié)構(gòu)體實例</returns>
public static T BytesToStructure<T>(byte[] bytes) where T : struct
{
 int size = Marshal.SizeOf(typeof(T));
 IntPtr ptr = Marshal.AllocHGlobal(size);
 try
 {
 Marshal.Copy(bytes, 0, ptr, size);
 return Marshal.PtrToStructure<T>(ptr);
 }
 finally
 {
 Marshal.FreeHGlobal(ptr);
 }
}

之后就只要抓一下包看看效果就好了。

抓包我們用SharpPcap,順便讓它幫我們過濾一下僅捕獲IP報文。代碼如下:

public static void Main(string[] args)
{
 CaptureDeviceList devices = CaptureDeviceList.Instance;
 if (devices.Count <= 0)
 {
 Console.WriteLine("No device found on this machine");
 return;
 }
 else
 {
 Console.WriteLine("available devices:");
 Console.WriteLine("-----------------------------");
 }

 int index = 0;
 foreach (ICaptureDevice item in devices)
 {
 Console.WriteLine($"{index++}) {item.Name}");
 }
 Console.Write("enter your choose: ");
 index = int.Parse(Console.ReadLine());
 Console.WriteLine();

 ICaptureDevice device = devices[index];
 device.OnPacketArrival += new PacketArrivalEventHandler((sender, e) =>
 {
 Packet packet = Packet.ParsePacket(e.Packet.LinkLayerType, e.Packet.Data);
 if (packet.Extract(typeof(IPPacket)) is IPPacket ipPacket)
 {
  IPv4Header header = StructHelper.BytesToStructure<IPv4Header>(ipPacket.Bytes);
  Console.WriteLine($"{header.SrcAddr} ==> {header.DstAddr}");
 }
 });

 device.Open(DeviceMode.Promiscuous, 1000);
 device.Filter = "ip";
 Console.CancelKeyPress += new ConsoleCancelEventHandler((sender, e) => device.Close());

 device.Capture();
}

啟動上面的代碼,選擇需要捕獲數(shù)據(jù)包的網(wǎng)卡,就可以看到此網(wǎng)卡上所有IP報文記錄及其源地址與目標(biāo)地址了:

三、大端字節(jié)序、小端字節(jié)序……

剛剛上面我們已經(jīng)成功的將byte[]對象轉(zhuǎn)換為我們想要的結(jié)構(gòu)體了,但我們轉(zhuǎn)換出來的結(jié)構(gòu)體真的正確嗎,我們可以將我們讀取出來的結(jié)構(gòu)體和PacketDotNet包解析出來的IP報文頭數(shù)據(jù)進行比較:

我們可以看到我們轉(zhuǎn)換出來的IPv4報文頭結(jié)構(gòu)體中的報文總長字段和PacketDotNet解析出來的數(shù)據(jù)不一致,我們的轉(zhuǎn)換函數(shù)出來的包總長是15872,而PacketDotNet解析出來的包總長只有62。

到底誰是對的呢,不用猜,肯定是我們的轉(zhuǎn)換函數(shù)有問題,如果看官您不相信,可以用WireShark抓包做比較,看看WireShark會挺誰的結(jié)果。

那么到底是哪里錯了呢?相信不少有實戰(zhàn)經(jīng)驗的看官已經(jīng)知道問題的原因了:大小字節(jié)序。

我們分別將15872和62轉(zhuǎn)為二進制格式:

數(shù)值 15872 62
二進制 00111110 00000000 00000000 00111110
 

15872和62這兩個數(shù)字轉(zhuǎn)換為二進制之后,15872的00111110在前面,00000000在后面,而62則正好相反。

一般來說計算機硬件有兩種儲存數(shù)據(jù)的方式:大端字節(jié)序(big endian)和小端字節(jié)序(little endian)。

舉例來說,數(shù)值0x2211使用兩個字節(jié)儲存:高位字節(jié)是0x22,低位字節(jié)是0x11。

大端字節(jié)序:

高位字節(jié)在前,低位字節(jié)在后,這是人類讀寫數(shù)值的方法。

小端字節(jié)序:

低位字節(jié)在前,高位字節(jié)在后,即以0x1122形式儲存。

在網(wǎng)絡(luò)中傳輸數(shù)據(jù),一般使用的是大端字節(jié)序,然而在計算機內(nèi)部中,為了方便計算,大多都會使用小端字節(jié)序進行儲存。

.net CLR默認(rèn)會使用當(dāng)前計算機系統(tǒng)使用的字節(jié)順序,而筆者測試時用的系統(tǒng)是Windows 7 x64,內(nèi)部默認(rèn)用的是小端字節(jié)序,所以在一切均為默認(rèn)的情況下,多字節(jié)字段在轉(zhuǎn)換后都會因為字節(jié)序不正確而讀取為錯誤值。

.net提供了一個屬性用于開發(fā)者獲取當(dāng)前計算機系統(tǒng)使用的字節(jié)序:

System.BitConverter.IsLittleEndian

如果此屬性為true,則表示當(dāng)前計算機正在使用小端字節(jié)序,否則為大端字節(jié)序。

回到剛剛的問題,為了防止大小端字節(jié)序?qū)D(zhuǎn)換產(chǎn)生影響,我們可以使用Attribute對結(jié)構(gòu)體中各個多字節(jié)字段進行標(biāo)記,并在轉(zhuǎn)換前判斷字節(jié)序是否一致,如果不一致則進行順序調(diào)整,代碼如下:

首先定義一個大小端字節(jié)序枚舉:

/// <summary>
/// 字節(jié)序枚舉
/// </summary>
public enum Endianness
{
 /// <summary>
 /// 大端字節(jié)序
 /// </summary>
 BigEndian,

 /// <summary>
 /// 小端字節(jié)序
 /// </summary>
 LittleEndian
}

然后定義大小端字節(jié)序聲明特性

/// <summary>
/// 字節(jié)序特性
/// </summary>
[AttributeUsage(AttributeTargets.Field)]
public class EndianAttribute : Attribute
{
 /// <summary>
 /// 標(biāo)記字段的字節(jié)序
 /// </summary>
 public Endianness Endianness { get; private set; }

 /// <summary>
 /// 構(gòu)造函數(shù)
 /// </summary>
 /// <param name="endianness">字節(jié)序</param>
 public EndianAttribute(Endianness endianness)
 {
 this.Endianness = endianness;
 }
}

我們在這里使用AttributeUsage特性限制此EndianAttribute特性僅限字段使用。

然后是轉(zhuǎn)換函數(shù):

/// <summary>
/// 調(diào)整字節(jié)順序
/// </summary>
/// <typeparam name="T">待調(diào)整字節(jié)順序的結(jié)構(gòu)體類型</typeparam>
/// <param name="bytes">字節(jié)數(shù)組</param>
private static byte[] RespectEndianness<T>(byte[] bytes)
{
 Type type = typeof(T);

 var fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
 .Where(f => f.IsDefined(typeof(EndianAttribute), false)).Select(field => new
 {
  Field = field,
  Attribute = (EndianAttribute)field.GetCustomAttributes(typeof(EndianAttribute), false).First(),
  Offset = Marshal.OffsetOf(type, field.Name).ToInt32()
 }).ToList();

 foreach (var field in fields)
 {
 if ((field.Attribute.Endianness == Endianness.BigEndian && BitConverter.IsLittleEndian) ||
 (field.Attribute.Endianness == Endianness.LittleEndian && !BitConverter.IsLittleEndian))
 {
  Array.Reverse(bytes, field.Offset, Marshal.SizeOf(field.Field.FieldType));
 }
 }

 return bytes;
}

此函數(shù)會先使用反射獲取所有含有EndianAttribute特性的公開或非公開實例字段,然后依次求出其偏移,最后判斷字段標(biāo)注的字節(jié)序是否與當(dāng)前計算機的字節(jié)序相同,如果不同則進行順序翻轉(zhuǎn)。另外上面的函數(shù)使用了Linq,需要引入System.Linq命名空間,且Linq函數(shù)中還使用到了匿名類。

接下來要對轉(zhuǎn)換函數(shù)進行修改,只需要在轉(zhuǎn)換前調(diào)用一下調(diào)序函數(shù)即可。

/// <summary>
/// 將 byte[] 轉(zhuǎn)為指定結(jié)構(gòu)體實例
/// </summary>
/// <typeparam name="T">目標(biāo)結(jié)構(gòu)體類型</typeparam>
/// <param name="bytes">待轉(zhuǎn)換 byte[]</param>
/// <returns>轉(zhuǎn)換后的結(jié)構(gòu)體實例</returns>
public static T BytesToStructure<T>(byte[] bytes) where T : struct
{
 bytes = RespectEndianness<T>(bytes);
 int size = Marshal.SizeOf(typeof(T));
 IntPtr ptr = Marshal.AllocHGlobal(size);
 try
 {
 Marshal.Copy(bytes, 0, ptr, size);
 return Marshal.PtrToStructure<T>(ptr);
 }
 finally
 {
 Marshal.FreeHGlobal(ptr);
 }
}

當(dāng)然了,我們還要對結(jié)構(gòu)體中的各個多字節(jié)字段標(biāo)記上EndianAttribute特性:

public struct IPv4Header
{
 /// <summary>
 /// IP協(xié)議版本及頭部長度
 /// </summary>
 private byte _verHlen;

 /// <summary>
 /// 差異化服務(wù)及顯式擁塞通告
 /// </summary>
 private byte _dscpEcn;

 /// <summary>
 /// 報文全長
 /// </summary>
 [Endian(Endianness.BigEndian)]
 private ushort _totalLength;

 /// <summary>
 /// 標(biāo)識符
 /// </summary>
 [Endian(Endianness.BigEndian)]
 private ushort _identification;

 /// <summary>
 /// 標(biāo)志位及分片偏移
 /// </summary>
 [Endian(Endianness.BigEndian)]
 private ushort _flagsOffset;

 /// <summary>
 /// 存活時間
 /// </summary>
 private byte _ttl;

 /// <summary>
 /// 協(xié)議
 /// </summary>
 private byte _protocol;

 /// <summary>
 /// 頭部檢驗和
 /// </summary>
 [Endian(Endianness.BigEndian)]
 private ushort _checksum;

 /// <summary>
 /// 源地址
 /// </summary>
 private int _srcAddr;

 /// <summary>
 /// 目標(biāo)地址
 /// </summary>
 private int _dstAddr;
}

需要說一點,就是最后的源地址和目標(biāo)地址,筆者上面用的是

public IPAddress(byte[] address)

這個構(gòu)造函數(shù)來構(gòu)造IPAddress類,并且是使用BitConverter.GetBytes這個方法將int類型轉(zhuǎn)為byte[]并傳入構(gòu)造函數(shù)的,所以不用注明大端序,否則會導(dǎo)致轉(zhuǎn)換結(jié)果不正確(錯誤結(jié)果和正確結(jié)果會正好顛倒)。 

重啟程序,看看現(xiàn)在我們的轉(zhuǎn)換函數(shù)轉(zhuǎn)換出來的結(jié)果是不是和PacketDotNet轉(zhuǎn)換結(jié)果一樣了?

四、性能提升!性能提升!

在解決了大字節(jié)序小字節(jié)序的問題之后,讓我們重新審視一下剛剛上面的轉(zhuǎn)換函數(shù),可以看到在剛才的函數(shù)中,每次要從byte[]中讀取結(jié)構(gòu)體時,都要經(jīng)過“申請堆外內(nèi)存——復(fù)制對象——讀取結(jié)構(gòu)體——釋放堆外內(nèi)存”這四步,申請堆外內(nèi)存,復(fù)制對象和釋放堆外內(nèi)存這三步照理來說是浪費性能的,明明byte[]已經(jīng)在內(nèi)存中了,但就是為了獲取它的安全句柄而大費周章的再去申請一塊內(nèi)存,畢竟申請和釋放內(nèi)存也算是一筆不小的開支了。

那除了Marshal.AllocHGlobal以外還有別的什么方法能獲取到托管對象的安全句柄呢?筆者又去網(wǎng)上找了一下,您還別說,這還真的有。朋友,您聽說過GCHandle嗎?

System.Runtime.InteropServices.GCHandle.Alloc

此方法允許傳入任意一個object對象,它將返回一個GCHandle實例并保護傳入的對象不會被GC回收掉,當(dāng)使用完畢后,需要調(diào)用此GCHandle實例的Free方法進行釋放。而GCHandle結(jié)構(gòu)體有一個實例方法AddrOfPinnedObject,此方法將返回此固定對象的地址及安全指針(IntPtr)。

利用GCHandle.Alloc方法,就可以避免重復(fù)的申請、復(fù)制和釋放內(nèi)存了,由此我們對剛剛的第一版BytesToStructure函數(shù)進行改進,第二版BytesToStructure函數(shù)閃亮登場:

/// <summary>
/// 將 byte[] 轉(zhuǎn)為指定結(jié)構(gòu)體實例
/// </summary>
/// <typeparam name="T">目標(biāo)結(jié)構(gòu)體類型</typeparam>
/// <param name="bytes">待轉(zhuǎn)換 byte[]</param>
/// <returns>轉(zhuǎn)換后的結(jié)構(gòu)體實例</returns>
public static T BytesToStructureV2<T>(byte[] bytes) where T : struct
{
 bytes = RespectEndianness<T>(bytes);
 GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
 try
 {
 return Marshal.PtrToStructure<T>(handle.AddrOfPinnedObject());
 }
 finally
 {
 handle.Free();
 }
}

現(xiàn)在我們來比較兩個轉(zhuǎn)換函數(shù)的效率試試:

我們使用相同的數(shù)據(jù)包,讓兩個轉(zhuǎn)換函數(shù)重復(fù)運行1000w次,查看兩個函數(shù)使用的時間差距:

注意:因為調(diào)整大小端字節(jié)序會使用到反射,會嚴(yán)重的影響到函數(shù)本身的運行效率(運行時間大部份都在用于反射),所以在測試時,筆者會注釋掉調(diào)整字節(jié)序調(diào)整的代碼。

BytesToStructure<T> BytesToStructureV2<T>
1000w次轉(zhuǎn)換耗時 5069 ms 2914 ms.

五、榨干潛能,使用不安全代碼!

我們在剛剛的代碼里通過避免“申請內(nèi)存——復(fù)制數(shù)據(jù)——釋放內(nèi)存”的步驟來提升函數(shù)的執(zhí)行效率,那經(jīng)過上面的改造,我們的轉(zhuǎn)換函數(shù)還有提升的空間嗎?

答案是有的。

C#和Java最大的不同點在于C#允許程序員使用不安全代碼,這里的不安全代碼并不是指一定存在漏洞會被攻擊者利用的不安全,而是使用指針的代碼。是的!C#允許使用指針!只需要在編譯時打開/unsafe開關(guān)。

文章一開始的C++代碼利用指針進行轉(zhuǎn)換,C#其實也可以:

/// <summary>
/// 將 byte[] 轉(zhuǎn)為指定結(jié)構(gòu)體實例
/// </summary>
/// <typeparam name="T">目標(biāo)結(jié)構(gòu)體類型</typeparam>
/// <param name="bytes">待轉(zhuǎn)換 byte[]</param>
/// <returns>轉(zhuǎn)換后的結(jié)構(gòu)體實例</returns>
public static unsafe T BytesToStructureV3<T>(byte[] bytes) where T : struct
{
 bytes = RespectEndianness<T>(bytes);
 fixed (byte* ptr = &bytes[0])
 {
 return (T)Marshal.PtrToStructure((IntPtr)ptr, typeof(T));
 }
}

這個第三版函數(shù)使用了兩個關(guān)鍵字unsafe和fixed,unsafe表示此代碼為不安全代碼,C#中不安全代碼必須在unsafe標(biāo)識區(qū)域內(nèi)使用,且編譯時要啟用/unsafe開關(guān)。fixed在這里主要是為了將指針?biāo)赶虻淖兞俊搬斪 ?,避免GC誤重定位變量以產(chǎn)生錯誤。

 

同樣,我們注釋掉大小端字節(jié)序調(diào)整函數(shù),再次重復(fù)運行1000w次,看看三個函數(shù)的用時:

BytesToStructure<T> BytesToStructureV2<T> BytesToStructureV3<T>
1000w次轉(zhuǎn)換耗時 5069 ms 2914 ms. 2004 ms

又比之前縮短了進1s的時間。當(dāng)然了因為這是重復(fù)1000w次的耗時,而因為我們注釋掉了大小端字節(jié)序調(diào)整函數(shù),實際情況下啟用大小端字節(jié)序調(diào)整函數(shù)的話,時間會爆炸性的增長??梢姺瓷涫且粋€多么浪費性能的操作。

六、小結(jié)

emmmmm,說一個比較尷尬的事情,其實本文討論的這種byte[]轉(zhuǎn)為結(jié)構(gòu)體的情況其實在日常開發(fā)中很少會用到,首先是因為結(jié)構(gòu)體這種數(shù)據(jù)結(jié)構(gòu)在日常開發(fā)中就很少會用到,平時開發(fā)的話類才是大頭,另外如果是因為要和C/C++開發(fā)的Dll交互,可以利用.net中System.Runtime.InteropServices命名空間下的StructLayoutAttribute、FieldOffset等特性自定義標(biāo)記結(jié)構(gòu)體的結(jié)構(gòu),CLR會在結(jié)構(gòu)體傳入或傳出時自動進行托管內(nèi)存與非托管內(nèi)存之間內(nèi)存格式的轉(zhuǎn)換。

所以本文其實是為了后面的博客做服務(wù)的,那就是不安全代碼,這個看似神秘,實則鋒利無比的雙刃劍,盡請期待。

相關(guān)文章

  • C#將配置文件appsetting中的值轉(zhuǎn)換為動態(tài)對象調(diào)用

    C#將配置文件appsetting中的值轉(zhuǎn)換為動態(tài)對象調(diào)用

    這篇文章主要介紹了將配置文件appsetting中的值轉(zhuǎn)換為動態(tài)對象調(diào)用 ,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下
    2018-09-09
  • C# 利用VS編寫一個簡單的網(wǎng)游客戶端

    C# 利用VS編寫一個簡單的網(wǎng)游客戶端

    本文主要介紹了在visual studio中利用C#編寫一個簡單的網(wǎng)游客戶端,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-12-12
  • 讓C# Excel導(dǎo)入導(dǎo)出 支持不同版本Office

    讓C# Excel導(dǎo)入導(dǎo)出 支持不同版本Office

    讓C# Excel導(dǎo)入導(dǎo)出,支持不同版本的Office,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-08-08
  • C#實現(xiàn)給DataGrid單元行添加雙擊事件的方法

    C#實現(xiàn)給DataGrid單元行添加雙擊事件的方法

    這篇文章主要介紹了C#實現(xiàn)給DataGrid單元行添加雙擊事件的方法,較為詳細(xì)的分析了C#給DataGrid單元添加雙擊事件的步驟及相關(guān)實現(xiàn)代碼,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-07-07
  • C# 反射(Reflection)的用處分析

    C# 反射(Reflection)的用處分析

    反射(Reflection)是C#里很重要的一個特性,其它語言也有這個特性,比如JAVA。反射這個特性是很實用的,如果使用過struts, hibernate, spring等等這些框架的話,便會知道反射這個特性是多么的強大了。在我接觸過的那些框架中,沒有一個框架是不使用反射的。
    2015-03-03
  • C#反色處理及其效率問題分析

    C#反色處理及其效率問題分析

    這篇文章主要介紹了C#反色處理及其效率問題分析,實例分析了C#反色處理問題的技巧及相關(guān)效率問題,需要的朋友可以參考下
    2015-06-06
  • 淺析C#封裝GRPC類庫及調(diào)用簡單實例

    淺析C#封裝GRPC類庫及調(diào)用簡單實例

    這篇文章主要為大家詳細(xì)介紹了C#中封裝GRPC類庫及調(diào)用簡單實例的相關(guān)知識,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2024-04-04
  • C# System.TypeInitializationException 異常處理方案

    C# System.TypeInitializationException 異常處理方案

    這篇文章主要介紹了C# System.TypeInitializationException 異常處理方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-02-02
  • C#設(shè)置子窗體在主窗體中居中顯示解決方案

    C#設(shè)置子窗體在主窗體中居中顯示解決方案

    接下來將介紹C#如何設(shè)置子窗體在主窗體中居中顯示,本文提供詳細(xì)的操作步驟,需要的朋友可以參考下
    2012-12-12
  • 利用C#實現(xiàn)獲取當(dāng)前設(shè)備硬件信息

    利用C#實現(xiàn)獲取當(dāng)前設(shè)備硬件信息

    這篇文章主要為大家詳細(xì)介紹了如何利用C#實現(xiàn)獲取當(dāng)前設(shè)備硬件信息的功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起了解一下
    2023-03-03

最新評論