与下位机或设备的通信解析优化的一点功能:T4+动态编译
一开始使用的方式是,从数据库读取出协议的配置,然后在接收到数据的时候,循环每个配置项根据配置-----解析数据------转换类型----存临时列表,,后来改进了一下,配置项存缓存,,数据库修改的时候,同时更新缓存。。
但还是慢了一点,因为需一个配置大概是20-30个数据项,每一条数据都要for循环20-30次 ,再加上还有N个根据配置的数据类型去做转换的if判断,这么一套下来,也很耗时间,但待解析的数据量大的情况下,,相对也很耗资源。。。
最后的觉得方案是:利用T4生成C#的class源码+运行时编译成类,数据直接扔class里直接解析出结果,不需要循环,也不需要if判断,因为在t4生成源码的时候,已经根据配置处理完了,因此节省了很多的时间。
不过由于T4模板的IDE支持的很不好,不过好在运行时T4模板在IDE内生成出来的类是partial的,因此,可以把大部分的代码,放在外部的C#文件里。先来看数据项的配置信息:
1 public class DataItem 2 { 3 /// <summary> 4 /// 数据项ID 5 /// </summary> 6 public ObjectId DataItemID { set; get; } 7 8 /// <summary> 9 /// 偏移量 10 /// </summary> 11 public int Pos { set; get; } 12 13 /// <summary> 14 /// 大小 15 /// </summary> 16 public int Size { set; get; } 17 18 public int BitIndex { set; get; } 19 20 /// <summary> 21 /// 数据项数据库储存类型 22 /// </summary> 23 public DbDataTypeEnum DbType { set; get; } 24 25 /// <summary> 26 /// 数据项协议源字节数组中的数据类型 27 /// </summary> 28 public DataTypeEnum SourceType { set; get; } 29 30 /// <summary> 31 /// 计算因子 32 /// </summary> 33 public decimal Factor { set; get; } 34 35 public string Key { set; get; } 36 } 37 38 /// <summary> 39 /// 对应的数据库字段类型 40 /// </summary> 41 public enum DbDataTypeEnum 42 { 43 Int32 = 0, 44 45 Int64 = 1, 46 47 Double = 2, 48 49 DateTime = 3, 50 51 Decimal = 4, 52 53 Boolean = 5 54 } 55 56 public enum DataTypeEnum 57 { 58 Int = 0, 59 60 Short = 1, 61 62 Datetime = 3, 63 64 Long = 5, 65 66 Decimal = 6, 67 68 UInt = 7, 69 70 Byte = 8, 71 72 Boolean = 9, 73 74 Bit = 10, 75 76 UShort = 11, 77 78 UByte = 12 79 }
这里为什么要区分源数据和数据库数据类型呢?主要是因为设备一般是int,short,double,float等类型,但是,对应到数据库,有时候比如说使用mongodb,之类的数据库,不一定有完全匹配的,因此需要区分两种数据项,
再来就是T4的模板 ProtocolExecuteTemplate.tt:
1 <#@ template language="C#" #> 2 <#@ assembly name="System.Core" #> 3 <#@ assembly name="Kugar.Core.NetCore" #> 4 <#@ assembly name="Kugar.Device.Service.BLL" #> 5 <#@ assembly name="Kugar.Device.Service.Data" #> 6 <#@ assembly name="MongoDB.Bson" #> 7 8 <#@ import namespace="System.Linq" #> 9 <#@ import namespace="System.Text" #> 10 <#@ import namespace="System.Collections.Generic" #> 11 <#@ import namespace="Kugar.Core.BaseStruct" #> 12 <#@ import namespace="MongoDB.Bson" #> 13 14 using System; 15 using System.Text; 16 using Kugar.Core.BaseStruct; 17 using Kugar.Core.ExtMethod; 18 using Kugar.Core.Log; 19 using Kugar.Device.Service.Data.DTO; 20 using Kugar.Device.Service.Data.Enums; 21 using MongoDB.Bson; 22 23 namespace Kugar.Device.Service.BLL 24 { 25 <# 26 var className="ProtocolExecutor_" + Protocol.Version.Replace('.','_') + "_" + this.GetNextClasID(); 27 #> 28 29 30 public class <#=className #>:IProtocolExecutor 31 { 32 private string _version=""; 33 private ObjectId _protocolID; 34 private readonly DateTime _baseDt=TimeZone.CurrentTimeZone.ToLocalTime(new System.DateTime(1970, 1, 1)); 35 36 public <#=className #> (ObjectId protocolID, string version) 37 { 38 _version=version; 39 _protocolID=protocolID; 40 } 41 42 public ObjectId ProtocolID {get{return _protocolID;}} 43 44 public BsonDocument Execute(byte[] data, int startIndex) 45 { 46 BsonDocument bson=new BsonDocument(); 47 <# 48 foreach(var item in Protocol.Items){ #> 49 bson["<#=item.Key #>"]= <#= DecodeConfig(item,0) #>; 50 <# 51 } 52 #> 53 54 return bson; 55 56 } 57 } 58 }
在直接在循环里输出解析后的语句,并且生成的类名记得后面加多一个随机数。。。。
然后再加一个ProtocolExecuteTemplate.Part.cs的部分类,补全T4模板的功能,因为在T4里IDE支持的不好,,写代码确实难受,,没直接写C#舒服:
1 public partial class ProtocolExecuteTemplate 2 { 3 private static int _classID = 0; 4 5 public ProtocolExecuteTemplate(DTO_ProtocolDataItem protocol) 6 { 7 Protocol = protocol; 8 9 } 10 11 12 13 public DTO_ProtocolDataItem Protocol { set; get; } 14 15 public string DecodeConfig(DTO_ProtocolDataItem.DataItem item,int startIndex) 16 { 17 var str = ""; 18 19 switch (item.SourceType) 20 { 21 case DataTypeEnum.Int: 22 str = $"BitConverter.ToInt32(data,startIndex + {startIndex + item.Pos})"; 23 break; 24 case DataTypeEnum.UInt: 25 str = $"BitConverter.ToUInt32(data,startIndex + {startIndex + item.Pos})"; 26 break; 27 case DataTypeEnum.Short: 28 str = $"BitConverter.ToInt16(data,startIndex + {startIndex + item.Pos})"; 29 break; 30 case DataTypeEnum.Long: 31 str= $"BitConverter.ToInt64(data,startIndex + {startIndex + item.Pos})"; 32 break; 33 case DataTypeEnum.Byte: 34 case DataTypeEnum.UByte: 35 case DataTypeEnum.Bit: 36 str = $"data[startIndex + {startIndex + item.Pos}]"; 37 break; 38 case DataTypeEnum.UShort: 39 str = $"BitConverter.ToUInt16(data,startIndex+{startIndex + item.Pos})"; 40 break; 41 default: 42 throw new ArgumentOutOfRangeException(); 43 } 44 45 if (item.SourceType==DataTypeEnum.Bit) 46 { 47 return byteToBit(str, item.BitIndex); 48 } 49 else 50 { 51 return valueTODBType(str, item.Factor, item.DbType); 52 } 53 } 54 55 private string valueTODBType(string sourceValue, decimal factor, DbDataTypeEnum dbType) 56 { 57 switch (dbType) 58 { 59 case DbDataTypeEnum.Int32: 60 return $" new BsonInt32({(factor > 0 ? $"(int)({factor}{getDecimalShortChar(factor)} * {sourceValue})" : sourceValue)})"; 61 case DbDataTypeEnum.Int64: 62 return $" new BsonInt64({(factor > 0 ? $"(long)({factor}{getDecimalShortChar(factor)} * {sourceValue})" : sourceValue)})"; 63 case DbDataTypeEnum.Double: 64 return $"new BsonDouble({(factor > 0 ? $"(double)({factor}{getDecimalShortChar(factor)} * {sourceValue})" : $"(double){sourceValue}")})"; 65 case DbDataTypeEnum.DateTime: 66 return $"new BsonDateTime(_baseDt.AddSeconds({sourceValue}))"; 67 case DbDataTypeEnum.Decimal: 68 return $"new Decimal128({(factor > 0 ? $"(decimal)({factor}{getDecimalShortChar(factor)} * {sourceValue})" : sourceValue)})"; 69 default: 70 throw new ArgumentOutOfRangeException(nameof(dbType), dbType, null); 71 } 72 } 73 74 private string byteToBit(string data, int index) 75 { 76 switch (index) 77 { 78 case 0: 79 { 80 81 return $"(({data} & 1) ==1)";//(data & 1) == 1; 82 } 83 case 1: 84 { 85 return $"(({data} & 2) ==1)";// (data & 2) == 2; 86 } 87 case 2: 88 { 89 return $"(({data} & 4) ==1)";//(data & 4) == 4; 90 } 91 case 3: 92 { 93 return $"(({data} & 8) ==1)";//(data & 8) == 8; 94 } 95 case 4: 96 { 97 return $"(({data} & 16) ==1)";//(data & 16) == 16; 98 } 99 case 5: 100 { 101 return $"(({data} & 32) ==1)";//(data & 32) == 32; 102 } 103 case 6: 104 { 105 return $"(({data} & 64) ==1)";//(data & 64) == 64; 106 } 107 case 7: 108 { 109 return $"(({data} & 128) ==1)";//(data & 128) == 128; 110 } 111 default: 112 throw new ArgumentOutOfRangeException(nameof(index)); 113 } 114 115 return $"(({data} & {index + 1}) ==1)"; 116 } 117 118 /// <summary> 119 /// 用于判断传入的fator是否需要使用deciaml进行运算,如果有小数点的,则是否decimal缩写m,,如果没有小数点,则使用普通的int类型 120 /// </summary> 121 /// <param name="value"></param> 122 /// <returns></returns> 123 private string getDecimalShortChar(decimal value) 124 { 125 return (value % 1) == 0 ? "" : "m"; 126 } 127 128 public int GetNextClasID() 129 { 130 return Interlocked.Increment(ref _classID); 131 } 132 }
这样,在运行时,即可直接生成可用于解析的类了,而且也不需要for循环判断,生成出来的类如:
1 public class ProtocolExecutor_1_1_000 2 { 3 public BsonDocument Execute(byte[] data, int startIndex) 4 { 5 BsonDocument bson = new BsonDocument(); 6 7 bson["项目名1"] = new BsonInt32(BitConverter.ToInt32(data, startIndex + 偏移量)); 8 bson["项目名2"] = new BsonInt32(BitConverter.ToInt32(data, startIndex + 偏移量)); 9 。。。。。。。 //其他数据项 10 11 return bson; 12 } 13 }
到这一步,就可以根绝配置项生成出对应的C#代码了,剩下的就是动态编译的事情了、将该代码编译出运行时Type,然后传入数据----解析出数据