C# 使用特性的方式封裝報文

逸羽澜心發表於2024-08-16

在編寫上位機軟體時,需要經常處理命令拼接與其他裝置進行通訊,通常對不同的命令封裝成不同的方法,擴充套件稍許麻煩。

本次擬以特性方式實現,以兼顧維護性與擴充套件性。


思想:

一種命令對應一個類,其類中的各個屬性對應各個命令段,透過特性的方式,實現其在這包資料命令中的位置、大端或小端及其轉換為對應的目標型別;

然後透過反射對其進行拼包,從而得到一包完整資料。

場景:

將一個軸移動到對應的X,Y,Z位置,為了演示,對其共用一個速度

這個移動到指定位置的命令假設按以下順序構成(為了展示,草率的命令結構):

序號 1 2 3 4 5 6 7 8 9
位元組 2 s32 u16 u16 u32 s32 s32 s32 2
說明 包頭 步驟號(ID) 功能碼 速度 X位置 Y位置 Z位置 包尾


實現:

建立特性 CmdPropertyAttribute

 1 [AttributeUsage(AttributeTargets.Property)]
 2 internal class CmdPropertyAttribute : Attribute
 3 {
 4     public Type? TargetType { get; set; }
 5 
 6     public int Number { get; set; }
 7 
 8     public bool IsReverse { get; set; }
 9 
10     public CmdPropertyAttribute(int number)
11     {
12         Number = number;
13     }
14 
15     public CmdPropertyAttribute(int number, Type targetType)
16     {
17         Number = number;
18         TargetType = targetType;
19     }
20 
21     public CmdPropertyAttribute(int number, bool isReverse)
22     {
23         Number = number;
24         IsReverse = isReverse;
25     }
26 
27     public CmdPropertyAttribute(int number, Type targetType, bool isReverse)
28     {
29         Number = number;
30         IsReverse = isReverse;
31         TargetType = targetType;
32     }
33 }

引數類,每一種命令對應一個引數類,它們繼承於引數基類

建立引數基類 ParamBase ,每種資料都是步驟號處於第一位,特把其放入到基類中

1     public class ParamBase
2     {
3         [CmdProperty(0, true)]
4         public int StepNum { get; set; }
5     }

建立軸列舉 Axis

1     public enum Axis : ushort
2     {
3         Axis_1 = 1,
4 
5         Axis_2 = 2,
6     }

建立功能碼列舉 FunctionCode

1     public enum FunctionCode
2     {
3         Move = 1
4     }

建立移動類 MoveParam ,為了更好展示高低位轉換,特對Speed屬性進行反轉

 1     public class MoveParam : ParamBase
 2     {
 3         [CmdProperty(1, typeof(ushort))]
 4         public FunctionCode Function { get; init; }
 5 
 6         [CmdProperty(2, typeof(ushort))]
 7         public Axis Axis { get; set; }
 8 
 9         [CmdProperty(3, true)]
10         public uint Speed { get; set; }
11 
12         [CmdProperty(4)]
13         public int XPoint { get; set; }
14 
15         [CmdProperty(5)]
16         public int YPoint { get; set; }
17 
18         [CmdProperty(6)]
19         public int ZPoint { get; set; }
20 
21         public MoveParam()
22         {
23             Function = FunctionCode.Move;
24         }
25 
26         public MoveParam(int stepNum, Axis axis, uint speed, int xPoint, int yPoint, int zPoint)
27         {
28             Function = FunctionCode.Move;
29             StepNum = stepNum;
30             Axis = axis;
31             Speed = speed;
32             XPoint = xPoint;
33             YPoint = yPoint;
34             ZPoint = zPoint;
35         }
36     }

對引數物件進行反射解析,生成對應的資料命令集合

建立擴充套件類 ParamBaseExtensions

 1     public static class ParamBaseExtensions
 2     {
 3         public static byte[] ToCmd(this ParamBase param)
 4         {
 5             var properties = param.GetType().GetProperties()
 6                 .Where(x => x.IsDefined(typeof(CmdPropertyAttribute), false))
 7                 .OrderBy(x => ((CmdPropertyAttribute)x.GetCustomAttribute(typeof(CmdPropertyAttribute))).Number);
 8 
 9             List<byte> result = new();
10 
11             foreach (var item in properties)
12             {
13                 var cmdAttribute = item.GetCustomAttribute(typeof(CmdPropertyAttribute)) as CmdPropertyAttribute;
14 
15                 var value = item.GetValue(param);
16 
17                 if (cmdAttribute.TargetType is not null)
18                 {
19                     value = Convert.ChangeType(value, cmdAttribute.TargetType);
20                 }
21 
22                 var propertyBytes = value.ToBytes();
23 
24                 if (cmdAttribute.IsReverse)
25                     propertyBytes = propertyBytes.Reverse().ToArray();
26 
27                 result.AddRange(propertyBytes);
28             }
29 
30             return result.ToArray();
31         }
32 
33 
34         private static byte[] ToBytes(this object obj)
35         {
36             return obj switch
37             {
38                 short s => BitConverter.GetBytes(s),
39                 ushort s => BitConverter.GetBytes(s),
40                 int s => BitConverter.GetBytes(s),
41                 uint s => BitConverter.GetBytes(s),
42                 float s => BitConverter.GetBytes(s),
43                 double s => BitConverter.GetBytes(s),
44                 byte s => [s],
45                 _ => throw new NotImplementedException(),
46             };
47         }
48     }

將資料命令與包頭,包尾拼接,從而組合成一包完整資料

建立類 CmdHelper

 1     public class CmdHelper
 2     {
 3         private byte[] GetHeads()
 4         {
 5             return [0x0B, 0x0F];
 6         }
 7 
 8 
 9         private byte[] GetTails()
10         {
11             return [0x0C, 0x0A];
12         }
13 
14         public byte[] BuilderCmd(ParamBase param)
15         {
16             return
17                 [
18                     .. GetHeads(),
19                     .. param.ToCmd(),
20                     .. GetTails(),
21                 ];
22         }
23     }

呼叫:

 1 var cmdHelper = new CmdHelper();
 2 var param = new MoveParam()
 3 {
 4     XPoint = 14,
 5     YPoint = 14,
 6     ZPoint = 14,
 7     Axis = Enums.Axis.Axis_1,
 8     Speed = 20,
 9     StepNum = 1
10 };
11 var byteArr = cmdHelper.BuilderCmd(param);
12 
13 foreach (var item in byteArr) 
14 {
15     Console.Write(item.ToString("X2") + " ");
16 }

最後的列印結果為:

0B 0F 00 00 00 01 00 00 01 00 00 00 00 14 0E 00 00 00 0E 00 00 00 0E 00 00 00 0C 0A

如果後續在寫其他命令,只需繼承於 ParamBase 類,在對應的屬性上使用 CmdProperty 特性即可

相關文章