关于如何在C#中根据配置表映射一个实体对象数据,我们常用有反射,虽然灵活但性能可能较低,尤其是在高频繁操作时。比如在数据采集的时候,开几个线程去跑数据,这个性能开销不是一星半点消耗了。
我们采用 表达式树(Expression Tree)
+ 委托缓存机制
的方式,确保每次转换时无需重复构建表达式逻辑,从而显著提升性能。表达式树调用比反射快 20 倍以上!
{
"PartList": [
{
"PartName": "绕线装置-升降1",
"PartAddress": "D2344",
"PartType": "RX_UpDown_1"
},
{
"PartName": "绕线装置-升降2",
"PartAddress": "D2380",
"PartType": "RX_UpDown_2"
},
{
"PartName": "抓线装置-升降1",
"PartAddress": "D2346",
"PartType": "ZX_UpDown_1"
},
{
"PartName": "抓线装置-升降2",
"PartAddress": "D2382",
"PartType": "ZX_UpDown_2"
}
]
}
// 装置配置项
public class PlcPart
{
public string PartName { get; set; }
public string PartAddress { get; set; }
public string PartType { get; set; }
public int PartAddressValue()
{
var match = _regex.Match(PartAddress);
return match.Success ? int.Parse(match.Groups[1].Value) : Convert.ToInt16(PartAddress.Substring(1));//获取元素地址
//return Convert.ToInt16(Element.Substring(2));//获取元素地址
}
}
public class RXJ_ActionData
{
public bool RX_UpDown_1 { get; set; }
public bool RX_UpDown_2 { get; set; }
public int ZX_UpDown_1 { get; set; }
public int ZX_UpDown_2 { get; set; }
}
flase
,1代表true
预编译委托(Expression Tree)
flase
,1代表true
)namespace DataCapture.Helper;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
public static class MapperCache
{
public static Func<Func<string, int>, T> GetMapper<T>(Dictionary<string, Delegate> mapperDelegates, PlcPart[] partList)
{
var key = GenerateKey(partList);
lock (mapperDelegates)
{
if (mapperDelegates.TryGetValue(key, out var del))
{
return (Func<Func<string, int>, T>)del;
}
del = BuildMapper<T>(partList);
mapperDelegates[key] = del;
return (Func<Func<string, int>, T>)del;
}
}
private static string GenerateKey(PlcPart[] partList)
{
return string.Join(";", partList.Select(p => $"{p.PartType}:{p.PartAddress}"));
}
private static Delegate BuildMapper<T>(PlcPart[] partList)
{
var getDataParam = Expression.Parameter(typeof(Func<string, int>), "getData");
var newExpr = Expression.New(typeof(T));
var bindings = new List<MemberBinding>();
foreach (var part in partList)
{
var propInfo = typeof(T).GetProperty(part.PartType);
if (propInfo == null || propInfo.GetSetMethod() == null)
continue;
var addressConst = Expression.Constant(part.PartAddress, typeof(string));
var rawValueExpr = Expression.Invoke(getDataParam, addressConst);
Expression convertedExpr;
if (propInfo.PropertyType == typeof(bool))
{
// int -> bool: value != 0
convertedExpr = Expression.NotEqual(rawValueExpr, Expression.Constant(0));
}
else
{
convertedExpr = rawValueExpr;
}
var bind = Expression.Bind(propInfo, convertedExpr);
bindings.Add(bind);
}
var memberInit = Expression.MemberInit(newExpr, bindings);
var lambda = Expression.Lambda<Func<Func<string, int>, T>>(
memberInit,
getDataParam
);
return lambda.Compile();
}
}
缓存机制
using System;
using System.Collections.Generic;
namespace DataCapture.DeviceDataSave;
///
/// 设备绕线机数据(根据自身定义)
///
public class DeviceRXJ
{
protected readonly Dictionary<string, Delegate> _cachedDelegates;
protected readonly Dictionary<string, int> _plcData = new Dictionary<string, int>();//数据缓存
private Func<Func<string, int>, RXJ_PLC_ActionDataModel> mapper;//表达式树
public DeviceRXJ( ElementConfig elementConfig)
{
//elementConfig是配置表内容(根据自身定义)
List<PlcPart> plcParts = new List<PlcPart>();
foreach (var moduleConfig in elementConfig.ModuleConfig)
{
plcParts.AddRange(moduleConfig.PartList);
}
mapper = MapperCache.GetMapper<RXJ_PLC_ActionDataModel>(_cachedDelegates, plcParts.ToArray());
}
///
/// 添加数据
///
///
///
public void AddData(string address, int value)
{
_plcData[address] = value;
}
///
/// 获取数据
///
///
///
public int GetData(string address)
{
//Console.WriteLine($"GetData: {address}");
return _plcData.TryGetValue(address, out var value) ? value : 0;
}
}
public static class PlcReader
{
private static readonly Dictionary<string, int> _plcData = new Dictionary<string, int>
{
{ "D2344", 1 }, // RX_UpDown_1 = true
{ "D2380", 0 }, // RX_UpDown_2 = false
{ "D2346", 123 }, // ZX_UpDown_1 = 123
{ "D2382", 456 } // ZX_UpDown_2 = 456
};
public static int ReadInt(string address)
{
return _plcData.TryGetValue(address, out var value) ? value : 0;
}
}
using System;
using System.Linq;
public class Program
{
public static void Main()
{
// 1. 解析字段配置表(模拟从 JSON 中读取)
var partList = new[]
{
new PlcPart{ PartName = "绕线装置-升降1", PartAddress = "D2344", PartType = "RX_UpDown_1" },
new PlcPart{ PartName = "绕线装置-升降2", PartAddress = "D2380", PartType = "RX_UpDown_2" },
new PlcPart{ PartName = "抓线装置-升降1", PartAddress = "D2346", PartType = "ZX_UpDown_1" },
new PlcPart{ PartName = "抓线装置-升降2", PartAddress = "D2382", PartType = "ZX_UpDown_2" }
};
// 2. 获取映射器(仅在首次构建一次)
var mapper = MapperCache.GetMapper(partList);
// 3. 从 PLC 获取数据(模拟)该处是委托,lambda表达式,回传回来的是地址,具体需求大家可以修改参数
Func<string, int> getData = address => PlcReader.ReadInt(address);
// 4. 执行映射,生成实体对象
var result = mapper(getData);
// 5. 输出结果
Console.WriteLine("映射结果:");
Console.WriteLine(result);
}
}
结果:
RX_UpDown_1: True, RX_UpDown_2: False, ZX_UpDown_1: 123, ZX_UpDown_2: 456
方法 | 冷启动 | 热运行 |
---|---|---|
反射 | 慢(需查找字段) | 慢(反射开销) |
预编译委托(本方案) | 较慢(生成委托) | 非常快(接近直接代码) |
冷启动 :首次运行时需生成委托,但后续调用无需重复生成。
热运行 :委托执行速度与直接代码赋值几乎相同,远快于反射。
表达式树调用比反射快 20 倍以上!
此方案在保持高性能的同时,提供了更灵活的接口设计,适用于动态配置和批量操作场景
支持更多类型映射 :如 float, short, DateTime 等
异常处理 :在 PLC 读取失败时加入重试机制或默认值
多线程安全 :确保 _cache 在并发访问下线程安全
日志记录 :记录映射过程,便于调试与维护