[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