使用C#表达式进行以成员命名为锚点的数据转换
在软件开辟中,有时由于某些原因如底层数据结构不可更改等需要将数据结构进行转换,这些数据类型之间没有继续关系,只有字段或属性名相同,往往需要手动编写数据转换代码,这样不仅繁琐,而且容易出错.
假如涉及到大量差别的类型转换,我们可以使用C#中的反射机制来转换数据.虽然使用反射很通用,但是还是太吃运行时间了,有没有一种既通用又高效的方法呢?
有的,兄弟有的,在C#中提供了动态创建表达式的功能,我们将基于C#表达式(Expression),设计一个通用的数据转换工具.
结构界说
起首,我们需要明确工具接口和一些结构界说.
我们的转换工具需要一个GetData方法,传入object,输出想要的数据结构;还需要一个标志表示某个类型是需要转换的,这个标志可以使用Attribute或接口表示,我们这里使用一个空的接口IConvertTarget来界说:- public interface IConvertTarget
- {
- }
- public static class DataTransformer
- {
- public static T GetData<T>(object source) where T : IConvertTarget, new()
- {
- }
- }
复制代码 传入source时我们需要一个转换器生存source->T的处理,这里界说一个委托类型DataTransformer来生存,差别的类型到类型的转换需要差别的转换器,为了可读性我们界说一个以[Type,Type]为索引的ConvertorDataBase结构:- public static class DataTransformer
- {
- private delegate IConvertTarget DataConvertor(object source);
-
- private class ConvertorDataBase
- {
- private readonly Dictionary<Type, Dictionary<Type, DataConvertor>> m_dictionary = new();
- public DataConvertor? this[Type targetType, Type sourceType]
- {
- get => m_dictionary.GetValueOrDefault(targetType)?.GetValueOrDefault(sourceType);
- set
- {
- if (value == null) return;
- m_dictionary.TryAdd(targetType, new Dictionary<Type, DataConvertor>());
- m_dictionary[targetType].Add(sourceType, value);
- }
- }
- public void ClearData()
- {
- m_dictionary.Clear();
- }
- }
- }
复制代码 GetData接口实现
GetData方法如下实现,_dataBase假如能找到convertor则直接调用,否则创建新的convertor,CreateConverter会在下文中实现.为CreateConverter和InvokeConverter添加Try Catch,便于发现接口中的异常.- public static class DataTransformer
- {
- private static ConvertorDataBase _dataBase = new();
- private static MethodInfo _getDataMethodInfo;
- static DataTransformer()
- {
- // GetData的Info,递归调用需要
- _getDataMethodInfo = typeof(DataTransformer).GetMethod(nameof(GetData)) ?? throw new InvalidOperationException();
- }
-
- public static T GetData<T>(object source) where T : IConvertTarget, new()
- {
- var targetType = typeof(T);
- var sourceType = source.GetType();
- var convertor = _dataBase[targetType, sourceType];
- if (convertor == null)
- {
- try
- {
- convertor = CreateConverter(targetType, sourceType);
- _dataBase[targetType, sourceType] = convertor;
- }
- catch (Exception e)
- {
- Console.Write("Something wrong when creating converter.\n" +
- $"targetType:{targetType}.\n" +
- $"sourceType:{sourceType}\n" +
- $"{e.Message}");
- throw;
- }
- }
- try
- {
- return (T)convertor(source);
- }
- catch (Exception e)
- {
- Console.Write("Something wrong when invoking converter.\n" +
- $"targetType:{targetType}.\n" +
- $"sourceType:{sourceType}\n" +
- $"{e.Message}");
- throw;
- }
- }
- public static void Reset() => _dataBase.ClearData();
- }
复制代码 CreateConverter方法实现
CreateConverter与详细实例无关,只需要关注输入输出的类型,这里就需要使用Expression来创建表达式.
我们的基本思路时先new一个目的实例,在使用成员初始化器设置字段和属性.- public static class DataTransformer
- {
- private static DataConvertor CreateConverter(Type targetType, Type sourceType)
- {
- // 参数obj的表达式
- var parameterExpression = Expression.Parameter(typeof(object), "obj");
- // 创建新的目标实例的表达式
- var targetInstanceExpression = Expression.New(targetType);
- // 用来保存确切类型的源数据的局部变量表达式
- var sourceInstanceExpression = Expression.Variable(sourceType, "source");
- // 获取target中所有公共的字段和属性
- var targetMembers = targetType.GetMembers(BindingFlags.Public | BindingFlags.Instance)
- .Where(info => info.MemberType is MemberTypes.Property or MemberTypes.Field).ToArray();
- // 获取source中所有公共的字段和属性
- var sourceMembers = sourceType.GetMembers(BindingFlags.Public | BindingFlags.Instance)
- .Where(info => info.MemberType is MemberTypes.Property or MemberTypes.Field).ToArray();
- //获取成员绑定表达式,具体实现在下文中
- var memberBindings = GetMemberBindings(targetMembers, sourceMembers, sourceInstanceExpression);
- //封装代码块
- var finalBlock = Expression.Block(
- new[]
- {
- //声明的局部变量source
- sourceInstanceExpression
- },
- //将obj强转后赋值给source
- Expression.Assign(sourceInstanceExpression, Expression.Convert(parameterExpression, sourceType)),
- //new Target 的 成员绑定
- Expression.MemberInit(targetInstanceExpression, memberBindings)
- );
- // 将表达式块编译为委托,返回一个 DataConvertor 类型的转换器.
- var lambda = Expression.Lambda<DataConvertor>(finalBlock, parameterExpression);
- return lambda.Compile();
- }
- }
复制代码 获取MemberBindings
简单的遍历,不表明.- public static class DataTransformer
- {
- private static IEnumerable<MemberBinding> GetMemberBindings(MemberInfo[] targetMembers, MemberInfo[] sourceMembers, ParameterExpression sourceInstanceExpression)
- {
- var memberBindings = new List<MemberBinding>();
- foreach (var targetMember in targetMembers)
- {
- var sourceMember = sourceMembers.FirstOrDefault(info => info.Name == targetMember.Name);
- if (sourceMember == null) continue;
- var sourceExpression = GetSourceExpression(targetMember, sourceMember, sourceInstanceExpression);
- if (sourceExpression != null)
- memberBindings.Add(Expression.Bind(targetMember, sourceExpression));
- }
- return memberBindings;
- }
- }
复制代码 获取SourceExpression
- public static class DataTransformer
- {
- private static Expression? GetSourceExpression(MemberInfo targetMember, MemberInfo sourceMember, ParameterExpression sourceInstanceExpression)
- {
- // 目标成员类型
- var targetMemberType = GetMemberType(targetMember);
- // 源成员类型
- var sourceMemberType = GetMemberType(sourceMember);
- // 获取源数据的成员表达式
- var sourceMemberExpression = Expression.PropertyOrField(sourceInstanceExpression, sourceMember.Name);
- // 如果类型可直接赋值(继承目标类型)直接返回成员表达式(直接赋值)
- if (targetMemberType.IsAssignableFrom(sourceMemberType))
- {
- return sourceMemberExpression;
- }
- // 如果目标类型也是IConvertTarget,则递归调用GetData
- if (typeof(IConvertTarget).IsAssignableFrom(targetMemberType))
- {
- return Expression.Call(_getDataMethodInfo.MakeGenericMethod(targetMemberType), sourceMemberExpression);
- }
- return null;
- }
- // 获取成员的类型
- private static Type GetMemberType(MemberInfo memberInfo)
- {
- if (memberInfo is FieldInfo fieldInfo)
- {
- return fieldInfo.FieldType;
- }
- if (memberInfo is PropertyInfo propertyInfo)
- {
- return propertyInfo.PropertyType;
- }
- throw new ArgumentException("MemberInfo must be of type FieldInfo, PropertyInfo or PropertyInfo.");
- }
- }
复制代码 完备代码
数据转换中经常会有List数据需要转换,完备代码中给出了实现,请自行查看,假如有字典类型等数据结构需要转换可以添加雷同的处理.(末端有彩蛋)- using System;
- using System.Collections;
- using System.Collections.Generic;
- using System.Linq;
- using System.Linq.Expressions;
- using System.Reflection;
- public interface IConvertTarget
- {
- }
- public static class DataTransformer
- {
- private delegate IConvertTarget DataConvertor(object source);
- private class ConvertorDataBase
- {
- private readonly Dictionary<Type, Dictionary<Type, DataConvertor>> m_dictionary = new();
- public DataConvertor? this[Type targetType, Type sourceType]
- {
- get => m_dictionary.GetValueOrDefault(targetType)?.GetValueOrDefault(sourceType);
- set
- {
- if (value == null) return;
- m_dictionary.TryAdd(targetType, new Dictionary<Type, DataConvertor>());
- m_dictionary[targetType].Add(sourceType, value);
- }
- }
- public void ClearData()
- {
- m_dictionary.Clear();
- }
- }
- private static ConvertorDataBase _dataBase = new();
- private static MethodInfo _getDataMethodInfo;
- private static MethodInfo _getListDataMethodInfo;
- static DataTransformer()
- {
- _getDataMethodInfo = typeof(DataTransformer).GetMethod(nameof(GetData)) ?? throw new InvalidOperationException();
- _getListDataMethodInfo = typeof(DataTransformer).GetMethod(nameof(GetDataList)) ?? throw new InvalidOperationException();
- }
- public static T GetData<T>(object source) where T : IConvertTarget, new()
- {
- var targetType = typeof(T);
- var sourceType = source.GetType();
- var convertor = _dataBase[targetType, sourceType];
- if (convertor == null)
- {
- try
- {
- convertor = CreateConverter(targetType, sourceType);
- _dataBase[targetType, sourceType] = convertor;
- }
- catch (Exception e)
- {
- Console.Write("Something wrong when creating converter.\n" +
- $"targetType:{targetType}.\n" +
- $"sourceType:{sourceType}\n" +
- $"{e.Message}");
- throw;
- }
- }
- try
- {
- return (T)convertor(source);
- }
- catch (Exception e)
- {
- Console.Write("Something wrong when invoking converter.\n" +
- $"targetType:{targetType}.\n" +
- $"sourceType:{sourceType}\n" +
- $"{e.Message}");
- throw;
- }
- }
- public static TList? GetDataList<TList, T>(IEnumerable? source) where T : IConvertTarget, new() where TList : IList<T>, new()
- {
- if (source == null)
- return default;
- var targetList = new TList();
- foreach (var obj in source)
- {
- targetList.Add(GetData<T>(obj));
- }
- return targetList;
- }
- public static void Reset() => _dataBase.ClearData();
- private static DataConvertor CreateConverter(Type targetType, Type sourceType)
- {
- var parameterExpression = Expression.Parameter(typeof(object), "obj");
- var targetInstanceExpression = Expression.New(targetType);
- var sourceInstanceExpression = Expression.Variable(sourceType, "source");
- var targetMembers = targetType.GetMembers(BindingFlags.Public | BindingFlags.Instance).Where(info => info.MemberType is MemberTypes.Property or MemberTypes.Field).ToArray();
- var sourceMembers = sourceType.GetMembers(BindingFlags.Public | BindingFlags.Instance).Where(info => info.MemberType is MemberTypes.Property or MemberTypes.Field).ToArray();
- var memberBindings = GetMemberBindings(targetMembers, sourceMembers, sourceInstanceExpression);
- var finalBlock = Expression.Block(
- new[]
- {
- sourceInstanceExpression
- },
- Expression.Assign(sourceInstanceExpression, Expression.Convert(parameterExpression, sourceType)),
- Expression.MemberInit(targetInstanceExpression, memberBindings)
- );
- var lambda = Expression.Lambda<DataConvertor>(finalBlock, parameterExpression);
- return lambda.Compile();
- }
- private static IEnumerable<MemberBinding> GetMemberBindings(MemberInfo[] targetMembers, MemberInfo[] sourceMembers, ParameterExpression sourceInstanceExpression)
- {
- var memberBindings = new List<MemberBinding>();
- foreach (var targetMember in targetMembers)
- {
- var sourceMember = sourceMembers.FirstOrDefault(info => info.Name == targetMember.Name);
- if (sourceMember == null) continue;
- var sourceExpression = GetSourceExpression(targetMember, sourceMember, sourceInstanceExpression);
- if (sourceExpression != null)
- memberBindings.Add(Expression.Bind(targetMember, sourceExpression));
- }
- return memberBindings;
- }
- private static Expression? GetSourceExpression(MemberInfo targetMember, MemberInfo sourceMember, ParameterExpression sourceInstanceExpression)
- {
- var targetMemberType = GetMemberType(targetMember);
- var sourceMemberType = GetMemberType(sourceMember);
- var sourceMemberExpression = Expression.PropertyOrField(sourceInstanceExpression, sourceMember.Name);
- if (targetMemberType.IsAssignableFrom(sourceMemberType))
- {
- return sourceMemberExpression;
- }
- if (typeof(IConvertTarget).IsAssignableFrom(targetMemberType))
- {
- return Expression.Call(_getDataMethodInfo.MakeGenericMethod(targetMemberType), sourceMemberExpression);
- }
- var itemType = targetMemberType.GetGenericArguments().FirstOrDefault();
- if (itemType != null && typeof(IList<>).MakeGenericType(itemType).IsAssignableFrom(targetMemberType) && typeof(IConvertTarget).IsAssignableFrom(itemType) && typeof(IEnumerable).IsAssignableFrom(sourceMemberType))
- {
- return Expression.Call(_getListDataMethodInfo.MakeGenericMethod(targetMemberType, itemType), sourceMemberExpression);
- }
- return null;
- }
- private static Type GetMemberType(MemberInfo memberInfo)
- {
- if (memberInfo is FieldInfo fieldInfo)
- {
- return fieldInfo.FieldType;
- }
- if (memberInfo is PropertyInfo propertyInfo)
- {
- return propertyInfo.PropertyType;
- }
- throw new ArgumentException("MemberInfo must be of type FieldInfo, PropertyInfo or PropertyInfo.");
- }
- }
复制代码 ???
有人说直接调方法也太没有手法了,实在获取List数据转换的还有更复杂的做法,做法如下:- private Expression GetListMemberAssignment(FieldInfo targetField, PropertyInfo sourceField, MemberExpression sourceList)
- {
- var targetElementType = targetField.FieldType.GetGenericArguments()[0];
- var sourceElementType = sourceField.PropertyType.GetGenericArguments()[0];
- var genericGetItemMethod = GetIndexerMethod(sourceElementType);//此处为获取Generic方法,太麻烦懒得写了
- var genericAddItemMethod = GetAddMethod(targetElementType);
- var targetList = Expression.Variable(targetField.FieldType, "targetList");
- var targetListInit = Expression.Assign(targetList, Expression.New(targetField.FieldType));
- //sourceList.Count
- var countExpression = Expression.Property(sourceList, m_countMethod);
- //var i = 0
- var loopVariable = Expression.Variable(typeof(int), "i");
- var loopVariableInit = Expression.Assign(loopVariable, Expression.Constant(0));
- //sourceList[i]
- var getItemMethod = Expression.Call(sourceList, genericGetItemMethod, loopVariable);
- //GetData<TargetType>(sourceList[i]);
- var getDataExpression = Expression.Call(m_thisInstance, m_getDataMethod.MakeGenericMethod(targetElementType), getItemMethod);
- //targetList.Add(GetData<TargetType>(sourceList[i]))
- var addItemMethod = Expression.Call(targetList, genericAddItemMethod, getDataExpression);
- var targetLabel = Expression.Label();
- var loopBody = Expression.Block(
- Expression.IfThen(
- Expression.Equal(loopVariable, countExpression),
- Expression.Break(targetLabel)
- ),
- addItemMethod,
- Expression.PostIncrementAssign(loopVariable) //i++
- );
- var loop = Expression.Loop(loopBody, targetLabel);
- var block = Expression.Block(new[] { loopVariable }, loopVariableInit, targetListInit, loop);
- var blockNull = Expression.Block(Expression.Assign(targetList, Expression.Constant(null, targetField.FieldType)));
- // 检查 sourceList 是否为空
- var ifThenElse = Expression.IfThenElse(
- Expression.Equal(sourceList, Expression.Constant(null)),
- blockNull,
- block
- );
- var ret = Expression.Block(new[] { targetList }, ifThenElse, targetList);
- return ret;
- }
复制代码 你就说有没有手法把.这种做法虽然工作量大,但实在效果也不如上一种写法,直接调用方法有泛型约束,并且编译的也更快.不过可以手写一下训练Expression的循环和条件的用法.
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |