目錄
- 1.背景
- 2.用類來封裝以太網心跳包的優缺點
- 2.1.優點
- 2.2.缺點
- 3.網路心跳包封裝類
- 4.實作IDisposable介面
- 5.應用層呼叫
- 6.Dispose()方法生效的測驗
- 7.測驗性能對比
- 8.綜上,在C#里,結構體主要作用有如下兩點:
1.背景
接上篇文章深入淺出C#結構體——封裝以太網心跳包的結構為例,使用結構體性能不佳,而且也說明了原因,本篇文章詳細描述了以類來封裝網路心跳包的優缺點,結果大大提升了決議性能,
2.用類來封裝以太網心跳包的優缺點
2.1.優點
- 可以在類里直接new byte[],即直接實體位元組陣列,然后寫初始化方法或者建構式中直接對傳進來的快取進行拷貝賦值;
- 無需裝箱拆箱;
- 類屬于參考型別,無需像結構體進行值拷貝,底層直接就是智能指標;
- 智能指標指向同一片記憶體,省記憶體空間;
- 可以在類里寫很多方便的方法,這也就是面向物件,面向領域的基石,方便以后擴展;
2.2.缺點
- 存在堆里,讀取性能會比堆疊稍差(現在PC端的計算速度很快,基本可忽略不計);
- 雖然類也屬于GC的托管資源,但是GC什么時候進行自動回收不可控制,需要實作IDisposable介面,用完該類,手動對該類進行釋放動作;
使用類的實際性能怎樣,我們用測驗資料說話,后面會放上與結構體測驗的性能對比資料,
3.網路心跳包封裝類
這里全部都命名成了位元組陣列,包括 public byte[] type=new byte[1];因為如果是byte type型別,我不知道如何去釋放這一值型別,怕到時候引起記憶體泄露等問題,然后在建構式里面將快取buf拷貝到了類的各個屬性中,就是這么簡單,
public class TcpHeartPacketClass: BaseDisposable
{
private bool _disposed; //表示是否已經被回收
public TcpHeartPacketClass(byte[] buf)
{
Buffer.BlockCopy(buf, 0, head, 0, 4);
type[0] = buf[4];
Buffer.BlockCopy(buf, 4, length, 0, 2);
Buffer.BlockCopy(buf, 6, Mac, 0, 6);
Buffer.BlockCopy(buf, 12, data, 0, 104);
Buffer.BlockCopy(buf, 116, tail, 0, 4);
}
protected override void Dispose(bool disposing)
{
if (!_disposed) //如果還沒有被回收
{
if (disposing) //如果需要回收一些托管資源
{
//TODO:回收托管資源,呼叫IDisposable的Dispose()方法就可以
}
//TODO:回收非托管資源,把之設定為null,等待CLR呼叫解構式的時候回收
head = null;
type = null;
length = null;
Mac = null;
data = https://www.cnblogs.com/JerryMouseLi/p/null;
tail = null;
_disposed = true;
}
base.Dispose(disposing);//再呼叫父類的垃圾回收邏輯
}
public byte[] head=new byte[4];
public byte[] type=new byte[1];
public byte[] length = new byte[2];
public byte[] Mac = new byte[6];
public byte[] data = new byte[104];//資料體
public byte[] tail = new byte[4];
}
4.實作IDisposable介面
用完類之后,為了主動去釋放類,我封裝了一個釋放基類BaseDisposable,詳見代碼注釋,有不明白的地方可以在評論區提問,我會詳細作答,
public class BaseDisposable : IDisposable
{
~BaseDisposable()
{
//垃圾回收器將呼叫該方法,因此引數需要為false,
Dispose(false);
}
/// <summary>
/// 是否已經呼叫了 Dispose(bool disposing)方法,
/// 應該定義成 private 的,這樣可以使基類和子類互不影響,
/// </summary>
private bool disposed = false;
/// <summary>
/// 所有回收作業都由該方法完成,
/// 子類應重寫(override)該方法,
/// </summary>
/// <param name="disposing"></param>
protected virtual void Dispose(bool disposing)
{
// 避免重復呼叫 Dispose ,
if (!disposed) return;
// 適應多執行緒環境,避免產生執行緒錯誤,
lock (this)
{
if (disposing)
{
// ------------------------------------------------
// 在此處寫釋放托管資源的代碼
// (1) 有 Dispose() 方法的,呼叫其 Dispose() 方法,
// (2) 沒有 Dispose() 方法的,將其設為 null,
// 例如:
// xxDataTable.Dispose();
// xxDataAdapter.Dispose();
// xxString = null;
// ------------------------------------------------
}
// ------------------------------------------------
// 在此處寫釋放非托管資源
// 例如:
// 檔案句柄等
// ------------------------------------------------
disposed = true;
}
}
/// <summary>
/// 該方法由程式呼叫,在呼叫該方法之后物件將被終結,
/// 該方法定義在IDisposable介面中,
/// </summary>
public void Dispose()
{
//因為是由程式呼叫該方法的,因此引數為true,
Dispose(true);
//因為我們不希望垃圾回收器再次終結物件,因此需要從終結串列中去除該物件,
GC.SuppressFinalize(this);
}
/// <summary>
/// 呼叫 Dispose() 方法,回收資源,
/// </summary>
public void Close()
{
Dispose();
}
}
5.應用層呼叫
DateTime packetClassStart = DateTime.Now;
TcpHeartPacketClass tcpHeartPacketClass = neTcpHeartPacketClass(ReceviveBuff);
DateTime packetClassEnd = DateTime.Now;
TimeSpan toClassTs = packetClassEnd.Subtra(packetClassStart);
try
{
tcpHeartPacketClass.head[0] = 0x11;
LoggerHelper.Info("類中的包頭:" + BitConverteToString(tcpHeartPacketClass.head));
Console.WriteLine("類中的包頭:{0}", BitConverteToString(tcpHeartPacketClass.head));
LoggerHelper.Info("類中的包型別:" tcpHeartPacketClass.type.ToString());
Console.WriteLine("類中的包型別:{0}"tcpHeartPacketClass.type.ToString());
LoggerHelper.Info("類中的包長度:" + BitConverteToString(tcpHeartPacketClass.length));
Console.WriteLine("類中的包長度:{0}", BitConverteToString(tcpHeartPacketClass.length));
LoggerHelper.Info("類中的MAC地址:" + BitConverteToString(tcpHeartPacketClass.Mac));
Console.WriteLine("類中的MAC地址:{0}", BitConverteToString(tcpHeartPacketClass.Mac));
LoggerHelper.Info("類中的注冊包內容:" + BitConverteToString(tcpHeartPacketClass.data));
Console.WriteLine("類中的注冊包內容:{0}"BitConverter.ToString(tcpHeartPacketClass.data));
LoggerHelper.Info("類中的包尾:" + BitConverteToString(tcpHeartPacketClass.tail));
Console.WriteLine("類中的包尾:{0}", BitConverteToString(tcpHeartPacketClass.tail));
Console.WriteLine("位元組陣列類中分割總共花費{0}ms\n"toClassTs.TotalMilliseconds);
}
finally
{
IDisposable disposable = tcpHeartPacketClass as IDisposable;
if (disposable != null)
disposable.Dispose();
}
6.Dispose()方法生效的測驗
在ty...finally塊執行完Dispose()方法之后,再去給類的某個屬性賦值,我們看是否報錯,如果報錯賦值給空物件則證明釋放成功,
finally
{
IDisposable disposable = tcpHeartPacketClass IDisposable;
if (disposable != null)
disposable.Dispose();
}
tcpHeartPacketClass.head[0] = 0x12;
如下報錯,翻譯過來意思就是物件參考沒有對應的實體,也就是被我們給釋放掉了,

7.測驗性能對比

通過上圖可以看到,上面的類決議的是微秒級別的,而文章深入淺出C#結構體——封裝以太網心跳包的結構為例決議的是幾十微秒級別的,差了差不多5到10倍的性能,
由此可見,在這種應用場景下,使用類來封裝網路心跳包比結構體封裝更合理,
8.綜上,在C#里,結構體主要作用有如下兩點:
- 資料長度很短,構造16位元組以下的新型別,而且結構體內的子型別必須是值型別,不然沒意義,其目的是為了適應堆疊上的高效讀取;
- 為了兼容一些來自C跟C++的庫;
避開以上兩點,我認為在C#新開發的應用程式中,可以完全的用類來取代結構體(僅代表個人觀點),
著作權宣告:本文為博主原創文章,遵循 CC 4.0 BY-SA 著作權協議,轉載請附上原文出處鏈接和本宣告,
本文鏈接:https://www.cnblogs.com/JerryMouseLi/p/12610332.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/56681.html
標籤:C#
下一篇:三、C#入門—資料型別
