.Net 利用表达式进行类型转换

主要思想:

  • 两个不同的类进行映射
  • 利用for循环找到相同的属性
  • 利用表达式把属性的赋值过程给存下来生成 表达式list
  • 真正使用的时候, 只需要读取表达式list进行赋值就好
namespace Common
{

    using NLog;
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using System.Linq.Expressions;

    public static class Mapper<TSource, TDestination>
        where TSource : class
        where TDestination : class, new()
    {
        
        private static readonly ILogger Logger = LogManager.GetCurrentClassLogger();

        private static readonly Type[] NotReferenceType = new Type[] { typeof(Google.Protobuf.WellKnownTypes.Timestamp) };

        public readonly static Func<TSource, TDestination> MapperFunc = GetMapperFunc();

        public readonly static Action<TSource, TDestination> MapperAction = GetMapperAction();

        public static TDestination MapTo(TSource source, Action<TSource, TDestination> action = null)
        {
            if (source == null)
            {
                Logger.Warn("The parameter ('source') cannot be null.");
                return null;
            }

            var target = MapperFunc(source);
            action?.Invoke(source, target);
            return target;
        }

        public static List<TDestination> MapToList(IEnumerable<TSource> sources, Action<TSource, TDestination> action = null)
        {
            if (sources == null)
            {
                Logger.Error($"The parameter ('sources') cannot be null.");
                throw new ArgumentNullException($"The parameter('sources') cannot be null.");
            }

            var list = new List<TDestination>();
            foreach (var item in sources)
            {
                list.Add(MapTo(item, action));
            }
            return list;
        }

        #region Private Method
        private static Func<TSource, TDestination> GetMapperFunc()
        {
            return source =>
            {
                var target = new TDestination();
                MapperAction(source, target);
                return target;
            };
        }

        private static Action<TSource, TDestination> GetMapperAction()
        {
            //获取源类型和指向类型
            var sourceType = typeof(TSource);
            var destinationType = typeof(TDestination);

            //创建Expression参数
            var sourceParameter = Expression.Parameter(sourceType, "x");
            var destinationParameter = Expression.Parameter(destinationType, "p");

            //创建Expression集合
            var expressionList = new List<Expression>();

            //获取TDestination中的所有允许写入权限的Public属性 或 RepeatedField 属性
            var destinationProperties = destinationType.GetProperties()
                .Where(x => (x.PropertyType.IsPublic || x.PropertyType.IsNestedPublic)
                            && (x.CanWrite || x.PropertyType.FullName.Contains(typeof(Google.Protobuf.Collections.RepeatedField<>).FullName)));

            var sourceProperties = sourceType.GetProperties()
                .Where(x => x.PropertyType.IsPublic || x.PropertyType.IsNestedPublic);
            foreach (var destinationPropertyInfo in destinationProperties)
            {
                try
                {
                    //获取匹配的TSource Property
                    var sourcePropertyInfo = sourceProperties.FirstOrDefault(x => string.Compare(x.Name, destinationPropertyInfo.Name) == 0);
                    if (sourcePropertyInfo == null || !sourcePropertyInfo.CanRead || sourcePropertyInfo.PropertyType.IsNotPublic)
                    {
                        continue;
                    }
                    //创建访问属性的表达式
                    var sourceProperty = Expression.Property(sourceParameter, sourcePropertyInfo);
                    var destinationProperty = Expression.Property(destinationParameter, destinationPropertyInfo);

                    //TSource Property不是值类型,并且和TDestination Property不相同
                    if (!sourcePropertyInfo.PropertyType.IsValueType
                        && !NotReferenceType.Contains(sourcePropertyInfo.PropertyType)
                        && sourcePropertyInfo.PropertyType != destinationPropertyInfo.PropertyType
                        && sourcePropertyInfo.PropertyType != typeof(string)
                        && destinationPropertyInfo.PropertyType != typeof(string))
                    {
                        //TSource Property和TDestination Property都是Class
                        if (sourcePropertyInfo.PropertyType.IsClass && destinationPropertyInfo.PropertyType.IsClass
                            && !sourcePropertyInfo.PropertyType.IsArray && !destinationPropertyInfo.PropertyType.IsArray
                            && !sourcePropertyInfo.PropertyType.IsGenericType && !destinationPropertyInfo.PropertyType.IsGenericType)
                        {
                            var expression = GetClassExpression(sourceProperty, sourcePropertyInfo.PropertyType, destinationPropertyInfo.PropertyType);
                            expressionList.Add(Expression.Assign(destinationProperty, expression));
                            continue;
                        }

                        //TSource派生自IEnumerable,TDestination类型为RepeatedField
                        //从C#IEnumerable转到Google ProtoBuffers RepeatedField
                        if (typeof(IEnumerable).IsAssignableFrom(sourcePropertyInfo.PropertyType)
                            && destinationPropertyInfo.PropertyType.FullName.Contains(typeof(Google.Protobuf.Collections.RepeatedField<>).FullName))
                        {
                            var test = Expression.NotEqual(sourceProperty, Expression.Constant(null, sourcePropertyInfo.PropertyType));

                            //获取List泛型参数
                            var sourceArgument = sourcePropertyInfo.PropertyType.IsArray
                                ? sourcePropertyInfo.PropertyType.GetElementType()
                                : sourcePropertyInfo.PropertyType.GetGenericArguments()[0];
                            var destinationArgument = sourcePropertyInfo.PropertyType.IsArray
                                ? destinationPropertyInfo.PropertyType.GetElementType()
                                : destinationPropertyInfo.PropertyType.GetGenericArguments()[0];

                            //构建方法主体
                            var mapperType = typeof(Mapper<,>).MakeGenericType(sourceArgument, destinationArgument);
                            var actionType = typeof(Action<,>).MakeGenericType(sourceArgument, destinationArgument);

                            var methodCall = Expression.Call(mapperType.GetMethod(nameof(MapToList)
                                , new Type[] { sourcePropertyInfo.PropertyType, actionType })
                                , sourceProperty
                                , Expression.Constant(null, actionType));

                            //构建p.columns.addrange(maptolist(x.columns, null))
                            var repeatedFieldType = typeof(Google.Protobuf.Collections.RepeatedField<>)
                                .MakeGenericType(destinationArgument);

                            var callExpression = Expression.Call(
                                destinationProperty
                                , repeatedFieldType.GetMethod("AddRange")
                                , methodCall);
                            var exp = Expression.IfThen(test, callExpression);
                            expressionList.Add(exp);
                            continue;
                        }

                        //TSource 派生自 RepeatedField,TDestination 类型为 IEnumerable
                        //从 Google ProtoBuffers RepeatedField 转到 C#IEnumerable
                        if (sourcePropertyInfo.PropertyType.FullName.Contains(typeof(Google.Protobuf.Collections.RepeatedField<>).FullName)
                            && typeof(IEnumerable).IsAssignableFrom(destinationPropertyInfo.PropertyType))
                        {
                            var test = Expression.NotEqual(sourceProperty, Expression.Constant(null, sourcePropertyInfo.PropertyType));

                            //获取List泛型参数
                            var sourceArgument = destinationPropertyInfo.PropertyType.IsArray
                                ? sourcePropertyInfo.PropertyType.GetElementType()
                                : sourcePropertyInfo.PropertyType.GetGenericArguments()[0];
                            var destinationArgument = destinationPropertyInfo.PropertyType.IsArray
                                ? destinationPropertyInfo.PropertyType.GetElementType()
                                : destinationPropertyInfo.PropertyType.GetGenericArguments()[0];

                            //构建方法主体
                            var mapperType = typeof(Mapper<,>).MakeGenericType(sourceArgument, destinationArgument);
                            var actionType = typeof(Action<,>).MakeGenericType(sourceArgument, destinationArgument);

                            var methodCall = Expression.Call(mapperType.GetMethod(nameof(MapToList)
                                , new Type[] { sourcePropertyInfo.PropertyType, actionType })
                                , sourceProperty
                                , Expression.Constant(null, actionType));

                            var result = Expression.Assign(destinationProperty, methodCall);

                            expressionList.Add(Expression.IfThen(test, result));
                            continue;
                        }

                        //TSource 和 TDestination都是直接或间接派生自IEnumerable
                        if (typeof(IEnumerable).IsAssignableFrom(sourcePropertyInfo.PropertyType)
                            && typeof(IEnumerable).IsAssignableFrom(destinationPropertyInfo.PropertyType))
                        {
                            var expression = GetEnumerableExpression(sourceProperty, sourcePropertyInfo.PropertyType, destinationPropertyInfo.PropertyType);
                            expressionList.Add(Expression.Assign(destinationProperty, expression));
                            continue;
                        }
                    }


                    //可空类型转换到非可空类型,当可空类型值为null时,用默认值赋给目标属性;不为null就直接转换
                    else if (IsNullable(sourcePropertyInfo.PropertyType) && !IsNullable(destinationPropertyInfo.PropertyType))
                    {
                        var hasValueExpression = Expression.Equal(Expression.Property(sourceProperty, "HasValue"), Expression.Constant(true));

                        //C# DateTime?类型转换到ProtoBuffers TimeStamp
                        if (sourcePropertyInfo.PropertyType.FullName.Contains(typeof(DateTime).FullName)
                            && destinationPropertyInfo.PropertyType.FullName.Contains(typeof(Google.Protobuf.WellKnownTypes.Timestamp).FullName))
                        {
                            var timestampType = typeof(Google.Protobuf.WellKnownTypes.Timestamp);

                            var valueExpression = Expression.Condition(hasValueExpression, Expression.Convert(sourceProperty,typeof(DateTime)), Expression.Default(typeof(DateTime)));
                            var callExpression = Expression.Call(valueExpression, typeof(DateTime).GetMethod("ToUniversalTime"));
                            var call = Expression.Call(timestampType.GetMethod("FromDateTime", new Type[] { typeof(DateTime) }), callExpression);
                            var conditionDateTime = Expression.Condition(hasValueExpression, call, Expression.Default(destinationPropertyInfo.PropertyType));
                            expressionList.Add(Expression.Assign(destinationProperty, conditionDateTime));
                        }
                        else 
                        {
                            var condition = Expression.Condition(hasValueExpression, Expression.Convert(sourceProperty, destinationPropertyInfo.PropertyType), Expression.Default(destinationPropertyInfo.PropertyType));
                            expressionList.Add(Expression.Assign(destinationProperty, condition));
                        }
                        continue;
                    }

                    //非可空类型转换到可空类型,直接转换
                    else if (!IsNullable(sourcePropertyInfo.PropertyType) && IsNullable(destinationPropertyInfo.PropertyType))
                    {
                        var memberExpression = Expression.Convert(sourceProperty, destinationPropertyInfo.PropertyType);
                        expressionList.Add(Expression.Assign(destinationProperty, memberExpression));
                        continue;
                    }

                    //ProtoBuffers TimeStamp类型转换到C# DateTime类型
                    else if (sourcePropertyInfo.PropertyType.FullName.Contains(typeof(Google.Protobuf.WellKnownTypes.Timestamp).FullName)
                        && destinationPropertyInfo.PropertyType.FullName.Contains(typeof(DateTime).FullName))
                    {
                        var timestampType = typeof(Google.Protobuf.WellKnownTypes.Timestamp);

                        //var callExpression = Expression.Call(timestampType, nameof(Google.Protobuf.WellKnownTypes.Timestamp.ToDateTime), new Type[] { timestampType }, sourceProperty)
                        var callExpression = Expression.Call(sourceProperty, timestampType.GetMethod("ToDateTime"));
                        expressionList.Add(Expression.Assign(destinationProperty, callExpression));
                        continue;
                    }

                    //C# DateTime类型转换到ProtoBuffers TimeStamp
                    else if (sourcePropertyInfo.PropertyType.FullName.Contains(typeof(DateTime).FullName)
                        && destinationPropertyInfo.PropertyType.FullName.Contains(typeof(Google.Protobuf.WellKnownTypes.Timestamp).FullName))
                    {
                        var timestampType = typeof(Google.Protobuf.WellKnownTypes.Timestamp);
                        var callExpression = Expression.Call(sourceProperty, typeof(DateTime).GetMethod("ToUniversalTime"));
                        var call = Expression.Call(timestampType.GetMethod("FromDateTime", new Type[] { typeof(DateTime) }), callExpression);
                        expressionList.Add(Expression.Assign(destinationProperty, call));
                        continue;
                    }

                    //String 转换 Guid
                    else if (sourcePropertyInfo.PropertyType.FullName.Contains(typeof(string).FullName)
                        && destinationPropertyInfo.PropertyType.FullName.Contains(typeof(Guid).FullName))
                    {
                        var call = Expression.Call(typeof(Guid).GetMethod("Prase", new Type[] { typeof(string) }), sourceProperty);
                        expressionList.Add(Expression.Assign(destinationProperty, call));
                        continue;
                    }

                    else if (sourcePropertyInfo.PropertyType.FullName.Contains(typeof(string).FullName)
                        && destinationPropertyInfo.PropertyType.FullName.Contains(typeof(string).FullName))
                    {
                        var test = Expression.NotEqual(sourceProperty, Expression.Constant(null, sourcePropertyInfo.PropertyType));
                        var ifThenExp = Expression.IfThen(test, Expression.Assign(destinationProperty, sourceProperty));
                        expressionList.Add(ifThenExp);
                        continue;
                    }

                    // Enum
                    else if (sourcePropertyInfo.PropertyType.IsEnum && destinationPropertyInfo.PropertyType.IsEnum)
                    {
                        var convertExpression = Expression.Convert(sourceProperty, destinationPropertyInfo.PropertyType);
                        expressionList.Add(Expression.Assign(destinationProperty, convertExpression));
                        continue;
                    }

                    else if (sourcePropertyInfo.PropertyType != destinationPropertyInfo.PropertyType)
                    {
                        continue;
                    }
                    var assignExpression = Expression.Assign(destinationProperty, sourceProperty);
                    expressionList.Add(assignExpression);
                }
                catch(ArgumentNullException ex)
                {
                    Logger.Error(ex);
                    throw new ArgumentNullException(ex.Message, ex);
                }
                catch(ArgumentException ex)
                {
                    Logger.Error(ex);
                    throw new ArgumentException(ex.Message, ex);
                }
                catch (Exception ex)
                {
                    Logger.Error(ex);
                    throw new InvalidOperationException(ex.Message, ex);
                }
            }

            //当TDestination != null,判断TSource是否为null
            var testSource = Expression.NotEqual(sourceParameter, Expression.Constant(null, sourceType));
            var ifTrueSource = Expression.Block(expressionList);
            var conditionSource = Expression.IfThen(testSource, ifTrueSource);

            //判断TDestination是否为空
            var testTarget = Expression.NotEqual(destinationParameter, Expression.Constant(null, destinationType));
            var conditionTarget = Expression.IfThen(testTarget, conditionSource);

            var lambda = Expression.Lambda<Action<TSource, TDestination>>(conditionTarget, sourceParameter, destinationParameter);
            return lambda.Compile();
        }

        private static Expression GetClassExpression(Expression sourceExpression, Type sourcePropertyType, Type destinationPropertyType)
        {
            try
            {
                var test = Expression.NotEqual(sourceExpression, Expression.Constant(null, sourcePropertyType));

                //构建方法主体
                var mapperManagerType = typeof(Mapper<,>).MakeGenericType(sourcePropertyType, destinationPropertyType);
                var actionType = typeof(Action<,>).MakeGenericType(sourcePropertyType, destinationPropertyType);

                var ifTrue = Expression.Call(mapperManagerType.GetMethod(nameof(MapTo), new Type[] { sourcePropertyType, actionType }), sourceExpression, Expression.Constant(null, actionType));
                var ifFalse = Expression.Constant(null, destinationPropertyType);

                var condition = Expression.Condition(test, ifTrue, ifFalse);

                return condition;
            }
            catch (Exception ex)
            {
                Logger.Error(ex);
                throw new ArgumentException(ex.Message, ex);
            }
        }

        private static Expression GetEnumerableExpression(Expression sourceExpression, Type sourcePropertyType, Type destinationPropertyType)
        {
            try
            {
                var test = Expression.NotEqual(sourceExpression, Expression.Constant(null, sourcePropertyType));

                //获取List泛型参数
                var sourceArgument = sourcePropertyType.IsArray ? sourcePropertyType.GetElementType() : sourcePropertyType.GetGenericArguments()[0];
                var destinationArgument = sourcePropertyType.IsArray ? destinationPropertyType.GetElementType() : sourcePropertyType.GetGenericArguments()[0];

                //构建方法主体
                var mapperManagerType = typeof(Mapper<,>).MakeGenericType(sourceArgument, destinationArgument);
                var actionType = typeof(Action<,>).MakeGenericType(sourceArgument, destinationArgument);

                var methodCall = Expression.Call(mapperManagerType.GetMethod(nameof(MapToList), new Type[] { sourcePropertyType, actionType }), sourceExpression, Expression.Constant(null, sourcePropertyType));

                Expression ifTrue;
                if (destinationPropertyType.Equals(methodCall.Type))
                {
                    ifTrue = methodCall;
                }
                else if (destinationPropertyType.IsArray)
                {
                    //数组,获取泛型TSource,调用ToArray方法转化
                    ifTrue = Expression.Call(typeof(Enumerable), nameof(Enumerable.ToArray), new[] { methodCall.Type.GenericTypeArguments[0] }, methodCall);
                }
                else if (typeof(IDictionary).IsAssignableFrom(destinationPropertyType))
                {
                    //字典,获取泛型TSource,调用ToDictionary方法转化
                    ifTrue = Expression.Call(typeof(Enumerable), nameof(Enumerable.ToDictionary), new[] { methodCall.Type.GenericTypeArguments[0] }, methodCall);
                }
                else
                {
                    ifTrue = Expression.Convert(methodCall, destinationPropertyType);
                }
                var ifFlase = Expression.Constant(null, destinationPropertyType);
                var conditionItem = Expression.Condition(test, ifTrue, ifFlase);

                return conditionItem;
            }
            catch (Exception ex)
            {
                Logger.Error(ex);
                throw new ArgumentException(ex.Message, ex);
            }
        }

        private static bool IsNullable(Type type)
        {
            return type.IsGenericType && type.GetGenericTypeDefinition().Equals(typeof(Nullable<>));
        }
        #endregion
    }
}


©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容