添加FastMapper

This commit is contained in:
2248356998 qq.com
2025-09-26 15:24:37 +08:00
parent 68e5a9c546
commit 31d6b2a9e6
9 changed files with 356 additions and 13 deletions

View File

@@ -0,0 +1,169 @@
using System.Collections.Concurrent;
using System.Linq.Expressions;
using System.Reflection;
namespace ThingsGateway.NewLife;
public class FastMapperOption
{
public Dictionary<string, string> MapperProperties { get; set; } = new();
public HashSet<string> IgnoreProperties { get; set; } = new();
}
public static class FastMapper
{
// 泛型 + 非泛型共用缓存
private static readonly ConcurrentDictionary<(Type Source, Type Target), Delegate> _mapCache
= new ConcurrentDictionary<(Type, Type), Delegate>();
#region
public static TTarget Mapper<TSource, TTarget>(TSource source, FastMapperOption option = null)
where TTarget : class, new()
{
if (source == null) return null;
var key = (typeof(TSource), typeof(TTarget));
if (!_mapCache.TryGetValue(key, out var del))
{
del = CreateMapFunc<TSource, TTarget>();
_mapCache[key] = del;
}
var func = (Func<TSource, FastMapperOption, TTarget>)del;
return func(source, option);
}
#endregion
#region
public static object Mapper(object source, Type targetType, FastMapperOption option = null)
{
if (source == null) return null;
var sourceType = source.GetType();
var key = (sourceType, targetType);
if (!_mapCache.TryGetValue(key, out var del))
{
// 动态生成泛型委托并缓存
var method = typeof(FastMapper).GetMethod(nameof(CreateMapFunc), BindingFlags.NonPublic | BindingFlags.Static);
var genericMethod = method.MakeGenericMethod(sourceType, targetType);
del = genericMethod.Invoke(null, null) as Delegate;
_mapCache[key] = del;
}
return del.DynamicInvoke(source, option);
}
#endregion
#region Expression Tree
private static Func<TSource, FastMapperOption, TTarget> CreateMapFunc<TSource, TTarget>()
where TTarget : class, new()
{
var sourceType = typeof(TSource);
var targetType = typeof(TTarget);
var sourceParam = Expression.Parameter(sourceType, "src");
var optionParam = Expression.Parameter(typeof(FastMapperOption), "opt");
var targetVar = Expression.Variable(targetType, "dest");
var expressions = new List<Expression>
{
Expression.Assign(targetVar, Expression.New(targetType))
};
var sourceProperties = sourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
var targetProperties = targetType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
var targetDict = targetProperties.Where(p => p.CanWrite).ToDictionary(p => p.Name);
foreach (var sp in sourceProperties)
{
if (!sp.CanRead) continue;
// FastMapperOption 重命名
var pNameExpr = Expression.Constant(sp.Name);
if (targetDict.TryGetValue(sp.Name, out PropertyInfo? tp))
{
// Ignore check
Expression ignoreCheck = Expression.Call(
Expression.Property(optionParam, nameof(FastMapperOption.IgnoreProperties)),
nameof(HashSet<string>.Contains),
null,
pNameExpr
);
Expression assign;
// 1⃣ 简单类型直接赋值
if (IsSimpleType(sp.PropertyType))
{
assign = Expression.Assign(
Expression.Property(targetVar, tp),
Expression.Convert(Expression.Property(sourceParam, sp), tp.PropertyType)
);
}
// 2⃣ 集合类型
else if (typeof(System.Collections.IEnumerable).IsAssignableFrom(sp.PropertyType) && sp.PropertyType != typeof(string))
{
var elementSourceType = sp.PropertyType.IsArray
? sp.PropertyType.GetElementType()
: sp.PropertyType.GetGenericArguments().FirstOrDefault();
var elementTargetType = tp.PropertyType.IsArray
? tp.PropertyType.GetElementType()
: tp.PropertyType.GetGenericArguments().FirstOrDefault();
if (elementSourceType != null && elementTargetType != null)
{
var mapListMethod = typeof(FastMapper).GetMethod(nameof(MapList), BindingFlags.Public | BindingFlags.Static)
.MakeGenericMethod(elementSourceType, elementTargetType);
assign = Expression.Assign(
Expression.Property(targetVar, tp),
Expression.Call(mapListMethod, Expression.Property(sourceParam, sp), optionParam)
);
}
else continue;
}
// 3⃣ 引用类型/嵌套对象
else
{
var mapMethod = typeof(FastMapper).GetMethod(nameof(Mapper), new Type[] { typeof(object), typeof(Type), typeof(FastMapperOption) });
var arg0 = Expression.Convert(Expression.Property(sourceParam, sp), typeof(object)); // ✅ fix Nullable/ValueType
assign = Expression.Assign(
Expression.Property(targetVar, tp),
Expression.Convert(
Expression.Call(mapMethod, arg0, Expression.Constant(tp.PropertyType), optionParam),
tp.PropertyType
)
);
}
expressions.Add(assign);
}
}
expressions.Add(targetVar);
var body = Expression.Block(new[] { targetVar }, expressions);
return Expression.Lambda<Func<TSource, FastMapperOption, TTarget>>(body, sourceParam, optionParam).Compile();
}
#endregion
#region
public static IEnumerable<TTarget> MapList<TSource, TTarget>(IEnumerable<TSource> list, FastMapperOption option = null)
where TTarget : class, new()
{
if (list == null) yield break;
foreach (var item in list)
yield return Mapper<TSource, TTarget>(item, option);
}
#endregion
private static bool IsSimpleType(Type type)
{
return type.IsPrimitive
|| type.IsEnum
|| type == typeof(string)
|| type == typeof(decimal)
|| type == typeof(DateTime)
|| (Nullable.GetUnderlyingType(type) != null && IsSimpleType(Nullable.GetUnderlyingType(type)));
}
}

View File

@@ -1,9 +1,9 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<PluginVersion>10.11.71</PluginVersion> <PluginVersion>10.11.72</PluginVersion>
<ProPluginVersion>10.11.71</ProPluginVersion> <ProPluginVersion>10.11.72</ProPluginVersion>
<DefaultVersion>10.11.71</DefaultVersion> <DefaultVersion>10.11.72</DefaultVersion>
<AuthenticationVersion>10.11.6</AuthenticationVersion> <AuthenticationVersion>10.11.6</AuthenticationVersion>
<SourceGeneratorVersion>10.11.6</SourceGeneratorVersion> <SourceGeneratorVersion>10.11.6</SourceGeneratorVersion>
<NET8Version>8.0.20</NET8Version> <NET8Version>8.0.20</NET8Version>

View File

@@ -9,6 +9,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
using ThingsGateway.Foundation.Extension.String; using ThingsGateway.Foundation.Extension.String;
using ThingsGateway.NewLife;
using TouchSocket.SerialPorts; using TouchSocket.SerialPorts;
@@ -140,7 +141,7 @@ public static class ChannelOptionsExtensions
/// <returns></returns> /// <returns></returns>
private static SerialPortChannel GetSerialPort(this TouchSocketConfig config, IChannelOptions channelOptions) private static SerialPortChannel GetSerialPort(this TouchSocketConfig config, IChannelOptions channelOptions)
{ {
var serialPortOption = channelOptions.Map<SerialPortOption>(); var serialPortOption = FastMapper.Mapper<IChannelOptions,SerialPortOption>(channelOptions);
serialPortOption.ThrowIfNull(nameof(SerialPortOption)); serialPortOption.ThrowIfNull(nameof(SerialPortOption));
channelOptions.Config = config; channelOptions.Config = config;
config.SetSerialPortOption(serialPortOption); config.SetSerialPortOption(serialPortOption);

View File

@@ -15,6 +15,7 @@ using System.Text;
using ThingsGateway.Foundation.Extension.Generic; using ThingsGateway.Foundation.Extension.Generic;
using ThingsGateway.Foundation.Extension.String; using ThingsGateway.Foundation.Extension.String;
using ThingsGateway.NewLife;
using TouchSocket.Resources; using TouchSocket.Resources;
@@ -187,8 +188,7 @@ public partial class ThingsGatewayBitConverter : IThingsGatewayBitConverter
// 更新设备地址为去除附加信息后的地址 // 更新设备地址为去除附加信息后的地址
registerAddress = sb.ToString(); registerAddress = sb.ToString();
var converter = (IThingsGatewayBitConverter)FastMapper.Mapper(this, type);
var converter = (IThingsGatewayBitConverter)this!.Map(type);
// 如果没有解析出任何附加信息,则直接返回默认的数据转换器 // 如果没有解析出任何附加信息,则直接返回默认的数据转换器
if (bcdFormat == null && stringlength == null && encoding == null && dataFormat == null && wstring == null) if (bcdFormat == null && stringlength == null && encoding == null && dataFormat == null && wstring == null)
{ {

View File

@@ -121,7 +121,7 @@ public class Device : BaseDataEntity, IValidatableObject
/// </summary> /// </summary>
[SugarColumn(ColumnDescription = "冗余扫描间隔")] [SugarColumn(ColumnDescription = "冗余扫描间隔")]
[AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)] [AutoGenerateColumn(Visible = true, Filterable = true, Sortable = true)]
[MinValue(30000)] [MinValue(10000)]
public virtual int RedundantScanIntervalTime { get; set; } = 30000; public virtual int RedundantScanIntervalTime { get; set; } = 30000;
/// <summary> /// <summary>

View File

@@ -330,6 +330,12 @@ internal sealed class DeviceThreadManage : IAsyncDisposable, IDeviceThreadManage
driver.IsInitSuccess = false; driver.IsInitSuccess = false;
LogMessage?.LogWarning(ex, string.Format(AppResource.InitFail, CurrentChannel.PluginName, driver?.DeviceName)); LogMessage?.LogWarning(ex, string.Format(AppResource.InitFail, CurrentChannel.PluginName, driver?.DeviceName));
} }
if (driver == null)
{
LogMessage?.LogWarning(string.Format(AppResource.InitFail, CurrentChannel.PluginName, driver?.DeviceName));
return;
}
if (driver?.DeviceId > 0) if (driver?.DeviceId > 0)
{ {
if (CancellationTokenSources.TryGetValue(driver.DeviceId, out var oldCts)) if (CancellationTokenSources.TryGetValue(driver.DeviceId, out var oldCts))

View File

@@ -0,0 +1,163 @@
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://kimdiego2098.github.io/
// QQ群605534569
//------------------------------------------------------------------------------
using BenchmarkDotNet.Diagnosers;
namespace ThingsGateway.Foundation;
using BenchmarkDotNet.Attributes;
using System;
using System.Collections.Generic;
using ThingsGateway.NewLife;
using TouchSocket.Core;
public class Foo
{
public string Name { get; set; }
public int Age { get; set; }
public int Age1 { get; set; }
public int Age2 { get; set; }
public int Age3 { get; set; }
public int Age11 { get; set; }
public int Age21 { get; set; }
public int Age31 { get; set; }
}
public class Bar
{
public string Name { get; set; }
public int Age { get; set; }
public Bar Child { get; set; }
public int Age1 { get; set; }
public int Age2 { get; set; }
public int Age3 { get; set; }
public int Age31 { get; set; }
public int Age32 { get; set; }
public int Age33 { get; set; }
}
[RankColumn]
[MemoryDiagnoser]
public class MapperBench
{
private List<Foo> foos;
[GlobalSetup]
public void Setup()
{
const int N = 100_000;
foos = new List<Foo>(N);
for (int i = 0; i < N; i++)
{
foos.Add(new Foo
{
Name = $"Name{i}",
Age = i,
});
}
}
[Benchmark(Baseline = true)]
public List<Bar> ManualConstructionMap100_000()
{
var bars = new List<Bar>(foos.Count);
foreach (var f in foos)
{
bars.Add(new Bar
{
Name = f.Name,
Age = f.Age,
});
}
return bars;
}
[Benchmark]
public List<Bar> ReflectionMap100_000()
{
var bars = new List<Bar>(foos.Count);
foreach (var f in foos)
{
bars.Add(OriginalMapper(f));
}
return bars;
}
[Benchmark]
public List<Bar> FastMapper100_000()
{
var bars = new List<Bar>(foos.Count);
foreach (var f in foos)
{
bars.Add(FastMapper.Mapper<Foo, Bar>(f));
}
return bars;
}
[Benchmark]
public List<Bar> ReflectionCachedMap100_000()
{
var bars = new List<Bar>(foos.Count);
foreach (var f in foos)
{
bars.Add(f.Map<Bar>());
}
return bars;
}
private static Bar OriginalMapper(Foo source)
{
if (source == null) return null;
var target = new Bar();
var st = typeof(Foo).GetProperties();
var tt = typeof(Bar).GetProperties();
foreach (var sp in st)
{
var tp = Array.Find(tt, x => x.Name == sp.Name);
if (tp == null || !tp.CanWrite) continue;
var value = sp.GetValue(source);
if (value == null)
{
tp.SetValue(target, null);
}
else if (IsSimpleType(sp.PropertyType))
{
// 基础类型直接赋值
tp.SetValue(target, value);
}
else
{
// 嵌套对象递归映射
tp.SetValue(target, value);
}
}
return target;
}
private static bool IsSimpleType(Type type)
{
return type.IsPrimitive || type.IsEnum || type == typeof(string) || type == typeof(decimal) || type == typeof(DateTime);
}
}

View File

@@ -27,8 +27,6 @@ namespace ThingsGateway.Foundation;
[SimpleJob(RuntimeMoniker.Net80)] [SimpleJob(RuntimeMoniker.Net80)]
//[SimpleJob(RuntimeMoniker.Net10_0)] //[SimpleJob(RuntimeMoniker.Net10_0)]
[MemoryDiagnoser] [MemoryDiagnoser]
[BaselineColumn]
[RankColumn]
public class S7Benchmark : IDisposable public class S7Benchmark : IDisposable
{ {
private List<SiemensS7Master> siemensS7s = new(); private List<SiemensS7Master> siemensS7s = new();

View File

@@ -45,10 +45,16 @@ namespace BenchmarkConsoleApp
//ManualConfig.Create(DefaultConfig.Instance) //ManualConfig.Create(DefaultConfig.Instance)
//.WithOptions(ConfigOptions.DisableOptimizationsValidator) //.WithOptions(ConfigOptions.DisableOptimizationsValidator)
//); //);
BenchmarkRunner.Run<ModbusBenchmark>(
ManualConfig.Create(DefaultConfig.Instance) BenchmarkRunner.Run<MapperBench>(
.WithOptions(ConfigOptions.DisableOptimizationsValidator) ManualConfig.Create(DefaultConfig.Instance)
); .WithOptions(ConfigOptions.DisableOptimizationsValidator)
);
// BenchmarkRunner.Run<ModbusBenchmark>(
// ManualConfig.Create(DefaultConfig.Instance)
// .WithOptions(ConfigOptions.DisableOptimizationsValidator)
//);
// BenchmarkRunner.Run<S7Benchmark>( // BenchmarkRunner.Run<S7Benchmark>(
//ManualConfig.Create(DefaultConfig.Instance) //ManualConfig.Create(DefaultConfig.Instance)
//.WithOptions(ConfigOptions.DisableOptimizationsValidator) //.WithOptions(ConfigOptions.DisableOptimizationsValidator)