[C#.NET] 利用 params 關鍵字重構方法
我用 [Modbus] 如何 用 C# 開發 Modbus Master Protocol - (04) 實作 TcpModbusRequest 的程式碼當範例,眼尖的人都能看出有相當多重覆的程式碼
每一個方法體都有自己的通訊格式建立的區段,重覆性相當的高
using (MemoryStream memory = new MemoryStream())
{
//TODO:通訊格式建立
}
分析一下方法結構體,如下圖:
詳細做法請參考:[Visual Studio 2012] 分析程式碼複製品
可以發現很多相似的程式碼散落在各處,受害的有以下類別
[Modbus] 如何 用 C# 開發 Modbus Master Protocol - (04) 實作 TcpModbusRequest
[Modbus] 如何 用 C# 開發 Modbus Master Protocol - (08) 實作 RtuModbusRequest
[Modbus] 如何 用 C# 開發 Modbus Master Protocol - (12) 實作 AsciiModbusRequest
Step1.利用 params 關鍵字,改善方法體。
在這個範例情境裡,建立通訊格式的方法體,大都是參數的數量不一樣,所以我利用 params 關鍵字來處理它們。
{ if (this.TransactionID != null) { this._transaction = TransactionID; } if (this._transaction == null) { this._transaction = 0; } ushort dataLength = 0; if (MultiOutputLength == null) { dataLength = MODBUS_DEFAULT_LENGTH; } else { dataLength = (ushort)(MODBUS_DEFAULT_LENGTH + MultiOutputLength + 1); } using (MemoryStream memory = new MemoryStream()) { memory.WriteByte((byte)(this._transaction >> 8)); memory.WriteByte((byte)this._transaction); memory.WriteByte((byte)(MODBUS_PROTOCOL >> 8)); memory.WriteByte((byte)MODBUS_PROTOCOL); memory.WriteByte((byte)(dataLength >> 8)); memory.WriteByte((byte)dataLength); memory.WriteByte((byte)Unit); memory.WriteByte((byte)FunctionCode); memory.Write(Parameters, 0, Parameters.Length); this._transaction++; return memory.ToArray(); } }
調用方式,貼上幾個重點的方法體
{ this.QuantityValidate(StartAddress, Quantity, 1, 2000); var parameters = new byte[] { (byte)(StartAddress >> 8), (byte)(StartAddress), (byte)(Quantity >> 8), (byte)Quantity }; var requestArray = this.CreateRequestMessage(Unit, EnumModbusFunctionCode.ReadCoils, null, parameters); return requestArray; } public override byte[] WriteMultipleCoils(byte Unit, ushort StartAddress, ushort Quantity, byte[] OutputValues) { this.QuantityValidate(StartAddress, Quantity, 1, 0x07B0); byte counter = base.GetMultiOutputCount(Quantity); if (counter != OutputValues.Length) { ModbusException.GetModbusException(0x03); } using (MemoryStream memoryStream = new MemoryStream()) { memoryStream.WriteByte((byte)(StartAddress >> 8)); memoryStream.WriteByte((byte)(StartAddress)); memoryStream.WriteByte((byte)(Quantity >> 8)); memoryStream.WriteByte((byte)(Quantity)); memoryStream.WriteByte((byte)(counter)); memoryStream.Write(OutputValues, 0, OutputValues.Length); var requestArray = this.CreateRequestMessage(Unit, EnumModbusFunctionCode.WriteMultipleCoils, (byte)OutputValues.Length, memoryStream.ToArray()); return requestArray; } }
Step2.執行單元測試
確保修正後的執行結果仍是正確
Step3.修改範本
通過測試後表示方法體能夠使用,接下來,將這些方法體都搬上 AbsModbusRequest 抽像類別範本,並定義 CreateRequestMessage 抽像方法:
有繼承 AbsModbusRequest 抽像類別的人只需要實作 CreateRequestMessage 方法就可以了,以下程式碼經過重構,已經減少了許多重覆性的程式碼,看起來也乾淨多了。
@TcpModbusRequest
{ private ushort? _transaction; private readonly ushort MODBUS_PROTOCOL = 0; private readonly ushort MODBUS_DEFAULT_LENGTH = 6; protected override byte[] CreateRequestMessage(byte Unit, EnumModbusFunctionCode FunctionCode, byte? MultiOutputLength, params byte[] Parameters) { if (this.TransactionID != null) { this._transaction = TransactionID; } if (this._transaction == null) { this._transaction = 0; } ushort dataLength = 0; if (MultiOutputLength == null) { dataLength = MODBUS_DEFAULT_LENGTH; } else { dataLength = (ushort)(MODBUS_DEFAULT_LENGTH + MultiOutputLength + 1); } using (MemoryStream memory = new MemoryStream()) { memory.WriteByte((byte)(this._transaction >> 8)); memory.WriteByte((byte)this._transaction); memory.WriteByte((byte)(MODBUS_PROTOCOL >> 8)); memory.WriteByte((byte)MODBUS_PROTOCOL); memory.WriteByte((byte)(dataLength >> 8)); memory.WriteByte((byte)dataLength); memory.WriteByte((byte)Unit); memory.WriteByte((byte)FunctionCode); memory.Write(Parameters, 0, Parameters.Length); this._transaction++; return memory.ToArray(); } } }
@RtuModbusRequest
{ protected override byte[] CreateRequestMessage(byte Unit, EnumModbusFunctionCode FunctionCode, byte? MultiOutputLength, params byte[] Parameters) { ushort dataLength = 0; if (MultiOutputLength == null) { dataLength = 0; } else { dataLength = (ushort)(MultiOutputLength + 1); } using (MemoryStream memory = new MemoryStream()) { memory.WriteByte((byte)Unit); memory.WriteByte((byte)FunctionCode); memory.Write(Parameters, 0, Parameters.Length); var crcArray = ModbusUtility.CalculateCRC(memory.ToArray()); memory.Write(crcArray, 0, crcArray.Length); return memory.ToArray(); } } }
@AsciiModbusRequest
{ protected override byte[] CreateRequestMessage(byte Unit, EnumModbusFunctionCode FunctionCode, byte? MultiOutputLength, params byte[] Parameters) { ushort dataLength = 0; if (MultiOutputLength == null) { dataLength = 0; } else { dataLength = (ushort)(MultiOutputLength + 1); } using (MemoryStream memory = new MemoryStream()) { memory.WriteByte(Unit); memory.WriteByte((byte)FunctionCode); memory.Write(Parameters, 0, Parameters.Length); var aduArray = toAsciiData(memory.ToArray()); return aduArray; } } private byte[] toAsciiData(byte[] OriginalArray) { var lrc = ModbusUtility.CalculateLRC(OriginalArray); var pdu = ModbusUtility.BytesToHexString(OriginalArray); var adu = Encoding.ASCII.GetBytes(ModbusUtility.ASCII_START_SYMBOL + pdu + lrc + ModbusUtility.ASCII_END_SYMBOL); return adu; } }
Step4.再次執行單元測試
如下圖:重構完的程式碼輸出結果不能夠有錯誤,所以一定要通過單元測試亮綠燈。
再次証明,若有寫單元測試,足以大大提升重構效益,寫一次單元測試,在開發的過程當中至少已經跑了至少2,30次以上,單元測試可以不斷的重覆使用,這絕對比麥當勞的超值午餐還要超值。
完整的專案請至
https://tako.codeplex.com/SourceControl/latest#746464
若有謬誤,煩請告知,新手發帖請多包涵
Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET