[C#.NET] 利用 params 關鍵字重構方法

  • 3427
  • 0
  • C#
  • 2016-01-21

[C#.NET] 利用 params 關鍵字重構方法

我用 [Modbus] 如何 用 C# 開發 Modbus Master Protocol - (04) 實作 TcpModbusRequest 的程式碼當範例,眼尖的人都能看出有相當多重覆的程式碼

每一個方法體都有自己的通訊格式建立的區段,重覆性相當的高
using (MemoryStream memory = new MemoryStream())
{
   //TODO:通訊格式建立
}

 

分析一下方法結構體,如下圖:

詳細做法請參考:[Visual Studio 2012] 分析程式碼複製品

image

 

可以發現很多相似的程式碼散落在各處,受害的有以下類別

[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.再次執行單元測試

如下圖:重構完的程式碼輸出結果不能夠有錯誤,所以一定要通過單元測試亮綠燈。

image

 

再次証明,若有寫單元測試,足以大大提升重構效益,寫一次單元測試,在開發的過程當中至少已經跑了至少2,30次以上,單元測試可以不斷的重覆使用,這絕對比麥當勞的超值午餐還要超值。

完整的專案請至

https://tako.codeplex.com/SourceControl/latest#746464

若有謬誤,煩請告知,新手發帖請多包涵


Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET

Image result for microsoft+mvp+logo