zoukankan      html  css  js  c++  java
  • 自己造轮子系列之OOM框架AutoMapper(记一次代码优化->ExpressionTree)

    【前言】

    OOM框架想必大家在Web开发中是使用频率非常之高的,如果还不甚了解OOM框架,那么我们对OOM框架稍作讲解。

    OOM顾名思义,Object-Object-Mapping实体间相互转换。常见的使用场景有两个实体要通过DTO对象进行页面的渲染,那么我们就需要通过对DTO对象的一个一个属性进行赋值,最终返回。整个过程是单调又繁琐的,甚至严重影响了代码的整洁性。更有强迫症高度患者可能看着这一坨shi一样的代码阵阵痉挛...因此,我们就想,能不能用一种简介的方法进行自动映射两个,三个甚至更多的对象,避免写这么一些低级乏味的代码。

    于是乎,一大波一大波的自动映射框架悠然而生。业内比较出名的有:AutoMapper,EmitMapper,NLiteMapper,TinyMapper等。在这里,先抛去他们不谈,毕竟未必个人觉得好用,或者我就喜欢造轮子。本篇将讲解一波自己造的轮子OOM框架AutoMapper,并且有造轮子过程中的持续优化,我想,这便是学习的最好过程吧。

    本文讲解了反射方式的实现方法以及后续抛弃反射的优化方法Expression Tree 表达式树的实现方法。

    【实现思路】

      AutoMapper既然是OOM框架,那么就离不开Object,Object必然又涉及到类型。那么,泛型是必不可少的。

      框架要简洁易用,并且能兼容多数场景。于是,我们准备从以下几方面进行设计:

    1. 支持自动映射,能通过调用一个方法自动映射实体;
    2. 支持特殊属性特殊处理的扩展功能,特殊处理的部分可以特殊进行赋值;
    3. 有不想进行映射的属性能够进行屏蔽;
    4. 映射的名称要支持配置(有名字不同的场景无法自动映射,手动配置映射关系进行映射);
    5. 配置不能加配置文件,通过标签的方式(约定优于配置);
    6. 性能要卓越;
    7. 代码要通用,版本支持度要高(使用.net standard类库,可同时支持.net framework 和 .netcore);

    【实现过程】

      项目结构:

      

      依据上面的思路,第一时间想到的便是通过反射获取到第一个对象的属性值,并动态创建第二个对象,遍历属性赋值,再添加细节处理。SoEasy!于是便兴冲冲完成了第一版。

      首先构造了几个特性标签类,用于标注是否要映射,以及映射的自定义别名的配置。

      DoNotMapperAttribute:不进行映射该属性

      MapperAttribute:添加自定义名称的映射配置

      MapperClassAttribute:该类支持映射(目前没有实际应用)

    1 using System;
    2 
    3 namespace SevenTiny.Bantina.AutoMapper
    4 {
    5     [AttributeUsage(AttributeTargets.Property, Inherited = true)]
    6     public class DoNotMapperAttribute :Attribute
    7     {
    8     }
    9 }
     1 using System;
     2 using System.Linq;
     3 using System.Reflection;
     4 
     5 namespace SevenTiny.Bantina.AutoMapper
     6 {
     7     [AttributeUsage(AttributeTargets.Property, Inherited = true)]
     8     public class MapperAttribute : Attribute
     9     {
    10         public string TargetName { get; set; }
    11 
    12         public MapperAttribute() { }
    13         public MapperAttribute(string targetName)
    14         {
    15             this.TargetName = targetName;
    16         }
    17 
    18         public static string GetTargetName(PropertyInfo property)
    19         {
    20             var attr = property.GetCustomAttributes<MapperAttribute>(true).FirstOrDefault();
    21             return attr != null ? (attr as MapperAttribute).TargetName ?? default(string) : default(string);
    22         }
    23     }
    24 }
     1 using System;
     2 using System.Linq;
     3 
     4 namespace SevenTiny.Bantina.AutoMapper
     5 {
     6     [AttributeUsage(AttributeTargets.Class | AttributeTargets.Field, Inherited = true)]
     7     public class MapperClassAttribute : Attribute
     8     {
     9         public string Name { get; set; }
    10         public static string GetName(Type type)
    11         {
    12             var attr = type.GetCustomAttributes(typeof(MapperClassAttribute), true).FirstOrDefault();
    13             return attr != null ? (attr as MapperClassAttribute).Name ?? default(string) : default(string);
    14         }
    15     }
    16 } 

    第一版反射实现代码:

     1 public sealed class Mapper
     2     {
     3         private Mapper() { }
     4         /// <summary>
     5         /// Init Source Value Dic
     6         /// </summary>
     7         /// <typeparam name="TSource"></typeparam>
     8         /// <param name="source"></param>
     9         /// <returns></returns>
    10         private static Dictionary<string, object> Initdic<TSource>(TSource source)
    11         {
    12             Dictionary<string, object> dic = new Dictionary<string, object>();
    13             foreach (PropertyInfo property in typeof(TSource).GetProperties())
    14             {
    15                 string targetPropertyName = MapperAttribute.GetTargetName(property);
    16                 if (!string.IsNullOrEmpty(targetPropertyName))
    17                 {
    18                     if (!dic.ContainsKey(targetPropertyName))
    19                     {
    20                         dic.Add(targetPropertyName, property.GetValue(source));
    21                     }
    22                 }
    23                 else if (!dic.ContainsKey(property.Name))
    24                 {
    25                     dic.Add(property.Name, property.GetValue(source));
    26                 }
    27             }
    28             return dic;
    29         }
    30         /// <summary>
    31         /// SetValue from propertyinfo and sourceDictionary
    32         /// </summary>
    33         /// <typeparam name="TValue"></typeparam>
    34         /// <param name="value"></param>
    35         /// <param name="propertyInfos"></param>
    36         /// <param name="sourceDic"></param>
    37         /// <returns></returns>
    38         private static TValue SetValue<TValue>(TValue value, PropertyInfo[] propertyInfos, Dictionary<string, object> sourceDic, Dictionary<string, string> keys) where TValue : class
    39         {
    40             foreach (PropertyInfo property in propertyInfos)
    41             {
    42                 if (!keys.ContainsKey(property.Name))
    43                 {
    44                     if (sourceDic.ContainsKey(property.Name))
    45                     {
    46                         try
    47                         {
    48                             property.SetValue(value, sourceDic[property.Name]);
    49                             keys.Add(property.Name, string.Empty);
    50                         }
    51                         catch (Exception)
    52                         {
    53                             property.SetValue(value, null);
    54                         }
    55                     }
    56                 }
    57             }
    58             return value;
    59         }
    60         /// <summary>
    61         /// AutoMapper
    62         /// </summary>
    63         /// <typeparam name="TValue">value type</typeparam>
    64         /// <typeparam name="TSource">source type</typeparam>
    65         /// <param name="source"></param>
    66         /// <returns></returns>
    67         public static TValue AutoMapper<TValue, TSource>(TSource source) where TValue : class where TSource : class
    68         {
    69             TValue value = Activator.CreateInstance<TValue>();
    70             PropertyInfo[] propertyInfos = typeof(TValue).GetProperties();
    71             Dictionary<string, string> keys = new Dictionary<string, string>();
    72             value = SetValue(value, propertyInfos, Initdic(source), keys);
    73             return value;
    74         }
    75         /// <summary>
    76         /// AutoMapper,Support for Use Action to custom special fields.
    77         /// </summary>
    78         /// <typeparam name="TValue"></typeparam>
    79         /// <typeparam name="TSource"></typeparam>
    80         /// <param name="source"></param>
    81         /// <param name="action"></param>
    82         /// <returns></returns>
    83         public static TValue AutoMapper<TValue, TSource>(TSource source, Action<TValue> action) where TValue : class where TSource : class
    84         {
    85             TValue value = Activator.CreateInstance<TValue>();
    86             PropertyInfo[] propertyInfos = typeof(TValue).GetProperties();
    87             Dictionary<string, string> keys = new Dictionary<string, string>();
    88             value = SetValue(value, propertyInfos, Initdic(source), keys);
    89             action(value);
    90             return value;
    91         }
    92     }

    由于思路清晰,实现起来也是很容易的。

    通过传入两个泛型(一个输入,一个输出),然后获取到其中一个的属性,并遍历另一个的属性,如果相同,则直接赋值(当然有特性标签部分的细节处理)。

    由于有些属性已经赋值过了,再次遇到会重新扫描赋值,降低了性能和影响了逻辑性,这里做了Dictionary的暂时缓存,以便扫描过的值不再进行赋值。

    特殊的值,我们使用了Action<T> action 匿名委托的方式进行赋值,提供了特殊处理的能力。

    我们下面进行一波使用,简单封装一个多次调用的测试方法:

     1 public static TimeSpan Caculate(int executTimes, Action action)
     2 {
     3    Stopwatch sw = new Stopwatch();
     4    sw.Start();
     5    for (int i = 0; i < executTimes; i++)
     6    {
     7      action();
     8    }
     9    sw.Stop();
    10    return sw.Elapsed;
    11 }

    简单写一个控制台应用程序,并传入参数100万次调用,并打印执行用时:

    1 Student1 stu1 = new Student1 { Uid = Guid.NewGuid() };
    2  Student5 stu5 = new Student5 { HealthLevel = 100, SchoolClass = new SchoolClass { Name = "class1" } };
    3 
    4  var test1 = StopwatchHelper.Caculate(1000000, () =>
    5  {
    6     Student stu = Mapper.AutoMapper<Student, Student5>(stu5, t => t.Name = "jony");
    7  });
    8  Console.WriteLine(test1.TotalMilliseconds);

    执行100w次大概需要3654毫秒,也就是3.6秒。

    虽说100万次小程序是可以忽略的,但是对于高并发的场景100万次再平常不过了,这个性能是不能忍受的!

    再看一眼直接调用的性能:

    1 Student5 stu5 = new Student5 { HealthLevel = 100, SchoolClass = new SchoolClass { Name = "class1" } };
    2 
    3 var test1 = StopwatchHelper.Caculate(1000000, () =>
    4 {
    5     Student stu = new Student { HealthLevel = stu5.HealthLevel, SchoolClass = stu5.SchoolClass };
    6 });
    7 Console.WriteLine(test1.TotalMilliseconds);

    直接最简单New对象赋值的操作耗时是100万次34毫秒,也就是0.034秒的样子差距还是挺大的。

    可见这样的性能是注定不可能成为一个好用的组件的!更别谈高性能了。

    于是便开始了在茫茫的代码库中淘取黑科技的一波操作。很感谢现代的搜索引擎,让我能在迷失的黑夜中找到一盏明灯。

    反射造成的性能问题是我迫切要解决的,反射性能的规避通常有以下几种方式进行提升:

    1. 缓存,高并发场景下绝对是要避免每次都去调用反射代码的,将反射部分尽量缓存下来。
    2. 调用C/C++代码库,但是首先要懂C/C++,其次要保证调用dll的时间要将反射的时间换取回来。
    3. 通过Expression Tree的方式构造表达式树,然后将表达式树缓存起来,这样在之后调用的代码都是等价于直接执行代码的。
    4. 通过IL Emit的方式动态构造代码,更为底层的IL代码提供更高的效率。

    在这四种方案中,我聚焦到了最后两种上。

    Activator.CreateInstance<TValue>()

    因为反射即便再怎么缓存,上述构建新实例的方法也是很耗费性能的(更别说由于临时工赶.net类库的问题,哈哈哈,太搞笑,可以参考此博文跟随老一辈程序员关于该方法性能的探讨以及源码的剖析http://www.cnblogs.com/leven/archive/2009/12/08/instanse_create_comparison.html),其次还有通过属性去获取值。这些都是难以通过缓存解决的。

    IL Emit的实现方式是比较复杂的,稍一不慎,还容易造成内存泄漏等严重的问题。让我一个半吊子程序员来写这么细致的代码,暂时选择回避。

    我选择了第三种:通过Expression Tree的方式构造表达式树,并且缓存委托方法进行调用的方法。表达式树这里就不进行详细讲解了,会放在其他博文里面单独讲解(也是一大块学问哦)。

    和第一版大同小异,也是通过泛型的思想进行构建。不同的是泛型从方法上传递改为了类上传递,这也是由于缓存Func部分的泛型难以直接传递转换的方式,这个对使用并无太大影响。

    第二版 Expression Tree 表达式树方式实现代码:

    为了代码通用,抽取了公共部分构造了一个内部的类进行对表达式树的构建操作:

     1 /*********************************************************
     2  * CopyRight: 7TINY CODE BUILDER. 
     3  * Version: 5.0.0
     4  * Author: 7tiny
     5  * Address: Earth
     6  * Create: 2018-04-09 16:55:16
     7  * Modify: 2018-04-09 16:55:16
     8  * E-mail: dong@7tiny.com | sevenTiny@foxmail.com 
     9  * GitHub: https://github.com/sevenTiny 
    10  * Personal web site: http://www.7tiny.com 
    11  * Technical WebSit: http://www.cnblogs.com/7tiny/ 
    12  * Description: 
    13  * Thx , Best Regards ~
    14  *********************************************************/
    15 using System;
    16 using System.Collections.Generic;
    17 using System.Linq;
    18 using System.Linq.Expressions;
    19 using System.Reflection;
    20 
    21 namespace SevenTiny.Bantina.AutoMapper
    22 {
    23     internal sealed class MapperExpressionCommon
    24     {
    25         /// <summary>
    26         /// structure func
    27         /// </summary>
    28         /// <param name="outType"></param>
    29         /// <param name="inTypes"></param>
    30         /// <param name="memberInitExpression"></param>
    31         /// <param name="parameterExpressionList"></param>
    32         public static void GetFunc(Type outType, Type[] inTypes, out MemberInitExpression memberInitExpression, out List<ParameterExpression> parameterExpressionList)
    33         {
    34             parameterExpressionList = new List<ParameterExpression>();
    35             List<MemberBinding> memberBindingList = new List<MemberBinding>();
    36             PropertyInfo[] propertyInfos = outType.GetProperties();
    37             Dictionary<string, PropertyInfo> outPropertyDic = propertyInfos.ToDictionary(t => t.Name, t => t);
    38             foreach (var inType in inTypes)
    39             {
    40                 ParameterExpression parameterExpression = Expression.Parameter(inType, inType.FullName);
    41                 PropertyInfo[] inTypePpropertyInfos = inType.GetProperties();
    42                 foreach (var inTypeInfo in inTypePpropertyInfos)
    43                 {
    44                     if (inTypeInfo.GetCustomAttribute(typeof(DoNotMapperAttribute)) == null)
    45                     {
    46                         //first
    47                         string outPropertyDicKey = MapperAttribute.GetTargetName(inTypeInfo);
    48                         //second
    49                         if (string.IsNullOrEmpty(outPropertyDicKey) && outPropertyDic.Keys.Contains(inTypeInfo.Name))
    50                         {
    51                             outPropertyDicKey = inTypeInfo.Name;
    52                         }
    53                         //third
    54                         if (!string.IsNullOrEmpty(outPropertyDicKey) && outPropertyDic.Keys.Contains(outPropertyDicKey))
    55                         {
    56                             MemberExpression property = Expression.Property(parameterExpression, inTypeInfo);
    57                             MemberBinding memberBinding = Expression.Bind(outPropertyDic[outPropertyDicKey], property);
    58                             memberBindingList.Add(memberBinding);
    59                             outPropertyDic.Remove(outPropertyDicKey);//remove property if has be valued
    60                         }
    61                     }
    62                 }
    63                 if (!parameterExpressionList.Exists(t => t.Name.Equals(parameterExpression.Name)))
    64                 {
    65                     parameterExpressionList.Add(parameterExpression);
    66                 }
    67             }
    68             memberInitExpression = Expression.MemberInit(Expression.New(outType), memberBindingList.ToArray());
    69         }
    70     }
    71 
    72 }

    在上面的这段代码中:

     ParameterExpression parameterExpression = Expression.Parameter(inType, inType.FullName); 

    构建了类似 t 的结构。

    MemberExpression property = Expression.Property(parameterExpression, inTypeInfo);
    MemberBinding memberBinding = Expression.Bind(outPropertyDic[outPropertyDicKey], property);

    构建了类似 HealthLevel = t.HealthLevel 的结构。

     1 memberInitExpression = Expression.MemberInit(Expression.New(outType), memberBindingList.ToArray()); 

    构建了类似 new Student() {HealthLevel = t.HealthLevel, SchoolClass = t.SchoolClass} 的结构。

    这段逻辑代码的调用方代码:

     1 /*********************************************************
     2  * CopyRight: 7TINY CODE BUILDER. 
     3  * Version: 5.0.0
     4  * Author: 7tiny
     5  * Address: Earth
     6  * Create: 2018-03-16 10:11:43
     7  * Modify: 2018-4-3 11:35:53
     8  * E-mail: dong@7tiny.com | sevenTiny@foxmail.com 
     9  * GitHub: https://github.com/sevenTiny 
    10  * Personal web site: http://www.7tiny.com 
    11  * Technical WebSit: http://www.cnblogs.com/7tiny/ 
    12  * Description: 
    13  * Thx , Best Regards ~
    14  *********************************************************/
    15 using System;
    16 using System.Collections.Generic;
    17 using System.Linq;
    18 using System.Linq.Expressions;
    19 using System.Reflection;
    20 
    21 namespace SevenTiny.Bantina.AutoMapper
    22 {
    23     public sealed class Mapper<TIn, TOut> where TOut : class where TIn : class
    24     {
    25         private Mapper() { }
    26         private static readonly Func<TIn, TOut> funcCache = GetFunc();
    27         public static TOut AutoMapper(TIn tIn)
    28         {
    29             return funcCache(tIn);
    30         }
    31         public static TOut AutoMapper(TIn tIn, Action<TOut> action)
    32         {
    33             TOut outValue = funcCache(tIn);
    34             action(outValue);
    35             return outValue;
    36         }
    37         private static Func<TIn, TOut> GetFunc()
    38         {
    39             Type[] types = new Type[] { typeof(TIn) };
    40             MemberInitExpression memberInitExpression;
    41             List<ParameterExpression> parameterExpressionList;
    42             MapperExpressionCommon.GetFunc(typeof(TOut), types, out memberInitExpression, out parameterExpressionList);
    43             Expression<Func<TIn, TOut>> lambda = Expression.Lambda<Func<TIn, TOut>>(memberInitExpression, parameterExpressionList);
    44             return lambda.Compile();
    45         }
    46     }
    47 }

    在上面这段代码中:

    Expression<Func<TIn, TOut>> lambda = Expression.Lambda<Func<TIn, TOut>>(memberInitExpression, parameterExpressionList);

    使用上面的公共方法out出的参数构建了一个完整的lambda表达式  t => new Student() {HealthLevel = t.HealthLevel, SchoolClass = t.SchoolClass}

    从代码断点中也可以看出:

    这里的t由于公共方法中使用的是 type(T).FullName,所以看起来比较长,是Test.SevenTiny.Bantina.Model.Student5,可以看成是一个小写的“t”,这里是等效的。

    lambda.Compile()

    将上述的表达式执行为Func<TIn,TOut> 的一个匿名委托。

    private static readonly Func<TIn, TOut> funcCache = GetFunc();

    将执行后的结果(匿名委托)缓存起来。

    这样最终的执行结果便转换成了一段代码:

    Func<Student5,Student> stuFunc = t=>new Student() {HealthLevel = t.HealthLevel, SchoolClass = t.SchoolClass};

    调用这段代码和直接实例化方法赋值是等效的,但是调用这段代码可以将很多通用的方法封装成一个公共的方法,提高了代码的重用性。

    这段代码耗时的部分便是通过一定的反射构建出一个Func<T,T2>所耗费的时间,这在数百万次的调用中仅仅调用了一次,而执行部分却是不耗费时间的,在高并发的场景下占有很大的优势。

    下面我们实际测试一下:

    同样的代码,使用了第二版的AutoMapper,执行100万次

    1 Student5 stu5 = new Student5 { HealthLevel = 100, SchoolClass = new SchoolClass { Name = "class1" } };
    2 
    3 var test1 = StopwatchHelper.Caculate(1000000, () =>
    4 {
    5     Student stu = Mapper<Student5, Student>.AutoMapper(stu5, t => t.Name = "jony");
    6 });
    7 Console.WriteLine(test1.TotalMilliseconds);

     我们加大调用的次数再次进行比较:

     1 Student5 stu5 = new Student5 { HealthLevel = 100, SchoolClass = new SchoolClass { Name = "class1" } };
     2 
     3 var test0 = StopwatchHelper.Caculate(1000000, () =>
     4 {
     5     Student stu = Mapper.AutoMapper<Student,Student5>(stu5, t => t.Name = "jony");
     6 });
     7 Console.WriteLine("使用反射调用 1 百万次耗时:");
     8 Console.WriteLine(test0.TotalMilliseconds);
     9 
    10 Console.WriteLine();
    11 
    12 var test1 = StopwatchHelper.Caculate(1000000, () =>
    13 {
    14     Student stu = Mapper<Student5, Student>.AutoMapper(stu5, t => t.Name = "jony");
    15 });
    16 Console.WriteLine("使用Expression表达式树调用 1 百万次耗时:");
    17 Console.WriteLine(test1.TotalMilliseconds);
    18 
    19 Console.WriteLine();
    20 
    21 var test2 = StopwatchHelper.Caculate(1000000, () =>
    22 {
    23     Student stu = new Student { HealthLevel = stu5.HealthLevel, Name = "jony" };
    24 });
    25 Console.WriteLine("使用代码直接构建 1 百万次耗时:");
    26 Console.WriteLine(test2.TotalMilliseconds);

    一百万次代码执行,Expression表达式树方式耗时0.084秒,而直接写代码赋值的方式耗时0.022秒,但是使用反射的方式却使用了3.6秒。

    执行次数越多,Expression表达式树的性能便越接近直接调用代码的方式。

    【总结】

    通过本次基础组建的完成以及一次优化,我从中学习到了Expression表达式树的实现方法以及突出的性能优势,虽然相比反射的写法更加复杂一些。

    在接下来的优化中,可能会加入对Emit的支持,到时候便是一场Emit和Expression的大战,不过我再次预测结果:应该差距不是很大~ 敬请期待...

    本文的完整代码以在我的github中开源,可以直接clone下来查看验证。

    https://github.com/sevenTiny/SevenTiny.Bantina

    下载下来的项目有多种基础组件,直接查看该组件,当然有对其他组件的意见或建议也可以直接提出来探讨,共同学习哦~

    不便于github查看的同学,我这里提供了完整的代码(是有多个对象映射的重载方法的完整版本)。

    公共Attribute特性标签部分:

     1 /*********************************************************
     2  * CopyRight: 7TINY CODE BUILDER. 
     3  * Version: 5.0.0
     4  * Author: 7tiny
     5  * Address: Earth
     6  * Create: 2018-04-09 16:55:16
     7  * Modify: 2018-04-09 16:55:16
     8  * E-mail: dong@7tiny.com | sevenTiny@foxmail.com 
     9  * GitHub: https://github.com/sevenTiny 
    10  * Personal web site: http://www.7tiny.com 
    11  * Technical WebSit: http://www.cnblogs.com/7tiny/ 
    12  * Description: 
    13  * Thx , Best Regards ~
    14  *********************************************************/
    15 using System;
    16 
    17 namespace SevenTiny.Bantina.AutoMapper
    18 {
    19     [AttributeUsage(AttributeTargets.Property, Inherited = true)]
    20     public class DoNotMapperAttribute :Attribute
    21     {
    22     }
    23 }
    View Code
     1 /*********************************************************
     2  * CopyRight: 7TINY CODE BUILDER. 
     3  * Version: 5.0.0
     4  * Author: 7tiny
     5  * Address: Earth
     6  * Create: 2018-04-03 13:28:38
     7  * Modify: 2018-04-03 13:28:38
     8  * E-mail: dong@7tiny.com | sevenTiny@foxmail.com 
     9  * GitHub: https://github.com/sevenTiny 
    10  * Personal web site: http://www.7tiny.com 
    11  * Technical WebSit: http://www.cnblogs.com/7tiny/ 
    12  * Description: 
    13  * Thx , Best Regards ~
    14  *********************************************************/
    15 using System;
    16 using System.Linq;
    17 using System.Reflection;
    18 
    19 namespace SevenTiny.Bantina.AutoMapper
    20 {
    21     [AttributeUsage(AttributeTargets.Property, Inherited = true)]
    22     public class MapperAttribute : Attribute
    23     {
    24         public string TargetName { get; set; }
    25 
    26         public MapperAttribute() { }
    27         public MapperAttribute(string targetName)
    28         {
    29             this.TargetName = targetName;
    30         }
    31 
    32         public static string GetTargetName(PropertyInfo property)
    33         {
    34             var attr = property.GetCustomAttributes<MapperAttribute>(true).FirstOrDefault();
    35             return attr != null ? (attr as MapperAttribute).TargetName ?? default(string) : default(string);
    36         }
    37     }
    38 }
    View Code
     1 /*********************************************************
     2  * CopyRight: 7TINY CODE BUILDER. 
     3  * Version: 5.0.0
     4  * Author: 7tiny
     5  * Address: Earth
     6  * Create: 2018-04-03 13:28:38
     7  * Modify: 2018-04-03 13:28:38
     8  * E-mail: dong@7tiny.com | sevenTiny@foxmail.com 
     9  * GitHub: https://github.com/sevenTiny 
    10  * Personal web site: http://www.7tiny.com 
    11  * Technical WebSit: http://www.cnblogs.com/7tiny/ 
    12  * Description: 
    13  * Thx , Best Regards ~
    14  *********************************************************/
    15 using System;
    16 using System.Linq;
    17 
    18 namespace SevenTiny.Bantina.AutoMapper
    19 {
    20     [AttributeUsage(AttributeTargets.Class | AttributeTargets.Field, Inherited = true)]
    21     public class MapperClassAttribute : Attribute
    22     {
    23         public string Name { get; set; }
    24         public static string GetName(Type type)
    25         {
    26             var attr = type.GetCustomAttributes(typeof(MapperClassAttribute), true).FirstOrDefault();
    27             return attr != null ? (attr as MapperClassAttribute).Name ?? default(string) : default(string);
    28         }
    29     }
    30 }
    View Code

    一、反射版本

      1 /*********************************************************
      2  * CopyRight: 7TINY CODE BUILDER. 
      3  * Version: 5.0.0
      4  * Author: 7tiny
      5  * Address: Earth
      6  * Create: 2018-03-16 10:11:43
      7  * Modify: 2018-4-3 11:35:53
      8  * E-mail: dong@7tiny.com | sevenTiny@foxmail.com 
      9  * GitHub: https://github.com/sevenTiny 
     10  * Personal web site: http://www.7tiny.com 
     11  * Technical WebSit: http://www.cnblogs.com/7tiny/ 
     12  * Description: 
     13  * Thx , Best Regards ~
     14  *********************************************************/
     15 using System;
     16 using System.Collections.Generic;
     17 using System.Linq;
     18 using System.Linq.Expressions;
     19 using System.Reflection;
     20 
     21 namespace SevenTiny.Bantina.AutoMapper
     22 {
     23     public sealed class Mapper
     24     {
     25         private Mapper() { }
     26         /// <summary>
     27         /// Init Source Value Dic
     28         /// </summary>
     29         /// <typeparam name="TSource"></typeparam>
     30         /// <param name="source"></param>
     31         /// <returns></returns>
     32         private static Dictionary<string, object> Initdic<TSource>(TSource source)
     33         {
     34             Dictionary<string, object> dic = new Dictionary<string, object>();
     35             foreach (PropertyInfo property in typeof(TSource).GetProperties())
     36             {
     37                 string targetPropertyName = MapperAttribute.GetTargetName(property);
     38                 if (!string.IsNullOrEmpty(targetPropertyName))
     39                 {
     40                     if (!dic.ContainsKey(targetPropertyName))
     41                     {
     42                         dic.Add(targetPropertyName, property.GetValue(source));
     43                     }
     44                 }
     45                 else if (!dic.ContainsKey(property.Name))
     46                 {
     47                     dic.Add(property.Name, property.GetValue(source));
     48                 }
     49             }
     50             return dic;
     51         }
     52         /// <summary>
     53         /// SetValue from propertyinfo and sourceDictionary
     54         /// </summary>
     55         /// <typeparam name="TValue"></typeparam>
     56         /// <param name="value"></param>
     57         /// <param name="propertyInfos"></param>
     58         /// <param name="sourceDic"></param>
     59         /// <returns></returns>
     60         private static TValue SetValue<TValue>(TValue value, PropertyInfo[] propertyInfos, Dictionary<string, object> sourceDic, Dictionary<string, string> keys) where TValue : class
     61         {
     62             foreach (PropertyInfo property in propertyInfos)
     63             {
     64                 if (!keys.ContainsKey(property.Name))
     65                 {
     66                     if (sourceDic.ContainsKey(property.Name))
     67                     {
     68                         try
     69                         {
     70                             property.SetValue(value, sourceDic[property.Name]);
     71                             keys.Add(property.Name, string.Empty);
     72                         }
     73                         catch (Exception)
     74                         {
     75                             property.SetValue(value, null);
     76                         }
     77                     }
     78                 }
     79             }
     80             return value;
     81         }
     82         /// <summary>
     83         /// AutoMapper
     84         /// </summary>
     85         /// <typeparam name="TValue">value type</typeparam>
     86         /// <typeparam name="TSource">source type</typeparam>
     87         /// <param name="source"></param>
     88         /// <returns></returns>
     89         public static TValue AutoMapper<TValue, TSource>(TSource source) where TValue : class where TSource : class
     90         {
     91             TValue value = Activator.CreateInstance<TValue>();
     92             PropertyInfo[] propertyInfos = typeof(TValue).GetProperties();
     93             Dictionary<string, string> keys = new Dictionary<string, string>();
     94             value = SetValue(value, propertyInfos, Initdic(source), keys);
     95             return value;
     96         }
     97         /// <summary>
     98         /// AutoMapper,Support for Use Action to custom special fields.
     99         /// </summary>
    100         /// <typeparam name="TValue"></typeparam>
    101         /// <typeparam name="TSource"></typeparam>
    102         /// <param name="source"></param>
    103         /// <param name="action"></param>
    104         /// <returns></returns>
    105         public static TValue AutoMapper<TValue, TSource>(TSource source, Action<TValue> action) where TValue : class where TSource : class
    106         {
    107             TValue value = Activator.CreateInstance<TValue>();
    108             PropertyInfo[] propertyInfos = typeof(TValue).GetProperties();
    109             Dictionary<string, string> keys = new Dictionary<string, string>();
    110             value = SetValue(value, propertyInfos, Initdic(source), keys);
    111             action(value);
    112             return value;
    113         }
    114         /// <summary>
    115         /// AutoMapper with multitype properties.
    116         /// </summary>
    117         /// <typeparam name="TValue"></typeparam>
    118         /// <typeparam name="TSource1"></typeparam>
    119         /// <typeparam name="TSource2"></typeparam>
    120         /// <param name="source1"></param>
    121         /// <param name="source2"></param>
    122         /// <returns></returns>
    123         public static TValue AutoMapper<TValue, TSource1, TSource2>(TSource1 source1, TSource2 source2) where TValue : class where TSource1 : class where TSource2 : class
    124         {
    125             TValue value = Activator.CreateInstance<TValue>();
    126             PropertyInfo[] propertyInfos = typeof(TValue).GetProperties();
    127             Dictionary<string, string> keys = new Dictionary<string, string>();
    128             value = SetValue(value, propertyInfos, Initdic(source1), keys);
    129             value = SetValue(value, propertyInfos, Initdic(source2), keys);
    130             return value;
    131         }
    132         /// <summary>
    133         /// AutoMapper with multitype properties.Support for Use Action to custom special fields.
    134         /// </summary>
    135         /// <typeparam name="TValue"></typeparam>
    136         /// <typeparam name="TSource1"></typeparam>
    137         /// <typeparam name="TSource2"></typeparam>
    138         /// <param name="source1"></param>
    139         /// <param name="source2"></param>
    140         /// <param name="action"></param>
    141         /// <returns></returns>
    142         public static TValue AutoMapper<TValue, TSource1, TSource2>(TSource1 source1, TSource2 source2, Action<TValue> action) where TValue : class where TSource1 : class where TSource2 : class
    143         {
    144             TValue value = Activator.CreateInstance<TValue>();
    145             PropertyInfo[] propertyInfos = typeof(TValue).GetProperties();
    146             Dictionary<string, string> keys = new Dictionary<string, string>();
    147             value = SetValue(value, propertyInfos, Initdic(source1), keys);
    148             value = SetValue(value, propertyInfos, Initdic(source2), keys);
    149             action(value);
    150             return value;
    151         }
    152         /// <summary>
    153         /// AutoMapper with multitype properties.
    154         /// </summary>
    155         /// <typeparam name="TValue"></typeparam>
    156         /// <typeparam name="TSource1"></typeparam>
    157         /// <typeparam name="TSource2"></typeparam>
    158         /// <typeparam name="TSource3"></typeparam>
    159         /// <param name="source1"></param>
    160         /// <param name="source2"></param>
    161         /// <param name="source3"></param>
    162         /// <returns></returns>
    163         public static TValue AutoMapper<TValue, TSource1, TSource2, TSource3>(TSource1 source1, TSource2 source2, TSource3 source3) where TValue : class where TSource1 : class where TSource2 : class where TSource3 : class
    164         {
    165             TValue value = Activator.CreateInstance<TValue>();
    166             PropertyInfo[] propertyInfos = typeof(TValue).GetProperties();
    167             Dictionary<string, string> keys = new Dictionary<string, string>();
    168             value = SetValue(value, propertyInfos, Initdic(source1), keys);
    169             value = SetValue(value, propertyInfos, Initdic(source2), keys);
    170             value = SetValue(value, propertyInfos, Initdic(source3), keys);
    171             return value;
    172         }
    173         /// <summary>
    174         /// AutoMapper with multitype properties.Support for Use Action to custom special fields.
    175         /// </summary>
    176         /// <typeparam name="TValue"></typeparam>
    177         /// <typeparam name="TSource1"></typeparam>
    178         /// <typeparam name="TSource2"></typeparam>
    179         /// <typeparam name="TSource3"></typeparam>
    180         /// <param name="source1"></param>
    181         /// <param name="source2"></param>
    182         /// <param name="source3"></param>
    183         /// <param name="action"></param>
    184         /// <returns></returns>
    185         public static TValue AutoMapper<TValue, TSource1, TSource2, TSource3>(TSource1 source1, TSource2 source2, TSource3 source3, Action<TValue> action) where TValue : class where TSource1 : class where TSource2 : class where TSource3 : class
    186         {
    187             TValue value = Activator.CreateInstance<TValue>();
    188             PropertyInfo[] propertyInfos = typeof(TValue).GetProperties();
    189             Dictionary<string, string> keys = new Dictionary<string, string>();
    190             value = SetValue(value, propertyInfos, Initdic(source1), keys);
    191             value = SetValue(value, propertyInfos, Initdic(source2), keys);
    192             value = SetValue(value, propertyInfos, Initdic(source3), keys);
    193             action(value);
    194             return value;
    195         }
    196         /// <summary>
    197         /// AutoMapper with multitype properties.
    198         /// </summary>
    199         /// <typeparam name="TValue"></typeparam>
    200         /// <typeparam name="TSource1"></typeparam>
    201         /// <typeparam name="TSource2"></typeparam>
    202         /// <typeparam name="TSource3"></typeparam>
    203         /// <typeparam name="TSource4"></typeparam>
    204         /// <param name="source1"></param>
    205         /// <param name="source2"></param>
    206         /// <param name="source3"></param>
    207         /// <param name="source4"></param>
    208         /// <returns></returns>
    209         public static TValue AutoMapper<TValue, TSource1, TSource2, TSource3, TSource4>(TSource1 source1, TSource2 source2, TSource3 source3, TSource4 source4) where TValue : class where TSource1 : class where TSource2 : class where TSource3 : class where TSource4 : class
    210         {
    211             TValue value = Activator.CreateInstance<TValue>();
    212             PropertyInfo[] propertyInfos = typeof(TValue).GetProperties();
    213             Dictionary<string, string> keys = new Dictionary<string, string>();
    214             value = SetValue(value, propertyInfos, Initdic(source1), keys);
    215             value = SetValue(value, propertyInfos, Initdic(source2), keys);
    216             value = SetValue(value, propertyInfos, Initdic(source3), keys);
    217             value = SetValue(value, propertyInfos, Initdic(source4), keys);
    218             return value;
    219         }
    220         /// <summary>
    221         /// AutoMapper with multitype properties.Support for Use Action to custom special fields.
    222         /// </summary>
    223         /// <typeparam name="TValue"></typeparam>
    224         /// <typeparam name="TSource1"></typeparam>
    225         /// <typeparam name="TSource2"></typeparam>
    226         /// <typeparam name="TSource3"></typeparam>
    227         /// <typeparam name="TSource4"></typeparam>
    228         /// <param name="source1"></param>
    229         /// <param name="source2"></param>
    230         /// <param name="source3"></param>
    231         /// <param name="source4"></param>
    232         /// <param name="action"></param>
    233         /// <returns></returns>
    234         public static TValue AutoMapper<TValue, TSource1, TSource2, TSource3, TSource4>(TSource1 source1, TSource2 source2, TSource3 source3, TSource4 source4, Action<TValue> action) where TValue : class where TSource1 : class where TSource2 : class where TSource3 : class where TSource4 : class
    235         {
    236             TValue value = Activator.CreateInstance<TValue>();
    237             PropertyInfo[] propertyInfos = typeof(TValue).GetProperties();
    238             Dictionary<string, string> keys = new Dictionary<string, string>();
    239             value = SetValue(value, propertyInfos, Initdic(source1), keys);
    240             value = SetValue(value, propertyInfos, Initdic(source2), keys);
    241             value = SetValue(value, propertyInfos, Initdic(source3), keys);
    242             value = SetValue(value, propertyInfos, Initdic(source4), keys);
    243             action(value);
    244             return value;
    245         }
    246         /// <summary>
    247         /// AutoMapper with multitype properties.
    248         /// </summary>
    249         /// <typeparam name="TValue"></typeparam>
    250         /// <typeparam name="TSource1"></typeparam>
    251         /// <typeparam name="TSource2"></typeparam>
    252         /// <typeparam name="TSource3"></typeparam>
    253         /// <typeparam name="TSource4"></typeparam>
    254         /// <typeparam name="TSource5"></typeparam>
    255         /// <param name="source1"></param>
    256         /// <param name="source2"></param>
    257         /// <param name="source3"></param>
    258         /// <param name="source4"></param>
    259         /// <param name="source5"></param>
    260         /// <returns></returns>
    261         public static TValue AutoMapper<TValue, TSource1, TSource2, TSource3, TSource4, TSource5>(TSource1 source1, TSource2 source2, TSource3 source3, TSource4 source4, TSource5 source5) where TValue : class where TSource1 : class where TSource2 : class where TSource3 : class where TSource4 : class where TSource5 : class
    262         {
    263             TValue value = Activator.CreateInstance<TValue>();
    264             PropertyInfo[] propertyInfos = typeof(TValue).GetProperties();
    265             Dictionary<string, string> keys = new Dictionary<string, string>();
    266             value = SetValue(value, propertyInfos, Initdic(source1), keys);
    267             value = SetValue(value, propertyInfos, Initdic(source2), keys);
    268             value = SetValue(value, propertyInfos, Initdic(source3), keys);
    269             value = SetValue(value, propertyInfos, Initdic(source4), keys);
    270             value = SetValue(value, propertyInfos, Initdic(source5), keys);
    271             return value;
    272         }
    273         /// <summary>
    274         /// AutoMapper with multitype properties.Support for Use Action to custom special fields.
    275         /// </summary>
    276         /// <typeparam name="TValue"></typeparam>
    277         /// <typeparam name="TSource1"></typeparam>
    278         /// <typeparam name="TSource2"></typeparam>
    279         /// <typeparam name="TSource3"></typeparam>
    280         /// <typeparam name="TSource4"></typeparam>
    281         /// <typeparam name="TSource5"></typeparam>
    282         /// <param name="source1"></param>
    283         /// <param name="source2"></param>
    284         /// <param name="source3"></param>
    285         /// <param name="source4"></param>
    286         /// <param name="source5"></param>
    287         /// <param name="action"></param>
    288         /// <returns></returns>
    289         public static TValue AutoMapper<TValue, TSource1, TSource2, TSource3, TSource4, TSource5>(TSource1 source1, TSource2 source2, TSource3 source3, TSource4 source4, TSource5 source5, Action<TValue> action) where TValue : class where TSource1 : class where TSource2 : class where TSource3 : class where TSource4 : class where TSource5 : class
    290         {
    291             TValue value = Activator.CreateInstance<TValue>();
    292             PropertyInfo[] propertyInfos = typeof(TValue).GetProperties();
    293             Dictionary<string, string> keys = new Dictionary<string, string>();
    294             value = SetValue(value, propertyInfos, Initdic(source1), keys);
    295             value = SetValue(value, propertyInfos, Initdic(source2), keys);
    296             value = SetValue(value, propertyInfos, Initdic(source3), keys);
    297             value = SetValue(value, propertyInfos, Initdic(source4), keys);
    298             value = SetValue(value, propertyInfos, Initdic(source5), keys);
    299             action(value);
    300             return value;
    301         }
    302     }
    303 }
    View Code

    二、Expression Tree 表达式树版本

      1 /*********************************************************
      2  * CopyRight: 7TINY CODE BUILDER. 
      3  * Version: 5.0.0
      4  * Author: 7tiny
      5  * Address: Earth
      6  * Create: 2018-03-16 10:11:43
      7  * Modify: 2018-4-3 11:35:53
      8  * E-mail: dong@7tiny.com | sevenTiny@foxmail.com 
      9  * GitHub: https://github.com/sevenTiny 
     10  * Personal web site: http://www.7tiny.com 
     11  * Technical WebSit: http://www.cnblogs.com/7tiny/ 
     12  * Description: 
     13  * Thx , Best Regards ~
     14  *********************************************************/
     15 using System;
     16 using System.Collections.Generic;
     17 using System.Linq;
     18 using System.Linq.Expressions;
     19 using System.Reflection;
     20 
     21 namespace SevenTiny.Bantina.AutoMapper
     22 {
     23     public sealed class Mapper<TIn, TOut> where TOut : class where TIn : class
     24     {
     25         private Mapper() { }
     26         private static readonly Func<TIn, TOut> funcCache = GetFunc();
     27         public static TOut AutoMapper(TIn tIn)
     28         {
     29             return funcCache(tIn);
     30         }
     31         public static TOut AutoMapper(TIn tIn, Action<TOut> action)
     32         {
     33             TOut outValue = funcCache(tIn);
     34             action(outValue);
     35             return outValue;
     36         }
     37         private static Func<TIn, TOut> GetFunc()
     38         {
     39             Type[] types = new Type[] { typeof(TIn) };
     40             MemberInitExpression memberInitExpression;
     41             List<ParameterExpression> parameterExpressionList;
     42             MapperExpressionCommon.GetFunc(typeof(TOut), types, out memberInitExpression, out parameterExpressionList);
     43             Expression<Func<TIn, TOut>> lambda = Expression.Lambda<Func<TIn, TOut>>(memberInitExpression, parameterExpressionList);
     44             return lambda.Compile();
     45         }
     46     }
     47     public sealed class Mapper<TIn1, TIn2, TOut> where TOut : class where TIn1 : class where TIn2 : class
     48     {
     49         private Mapper() { }
     50         public static TOut AutoMapper(TIn1 tIn1, TIn2 tIn2)
     51         {
     52             return funcCache(tIn1, tIn2);
     53         }
     54         public static TOut AutoMapper(TIn1 tIn1, TIn2 tIn2, Action<TOut> action)
     55         {
     56             TOut outValue = funcCache(tIn1, tIn2);
     57             action(outValue);
     58             return outValue;
     59         }
     60         private static readonly Func<TIn1, TIn2, TOut> funcCache = GetFunc();
     61         private static Func<TIn1, TIn2, TOut> GetFunc()
     62         {
     63             Type[] types = new Type[] { typeof(TIn1), typeof(TIn2) };
     64             MemberInitExpression memberInitExpression;
     65             List<ParameterExpression> parameterExpressionList;
     66             MapperExpressionCommon.GetFunc(typeof(TOut), types, out memberInitExpression, out parameterExpressionList);
     67             Expression<Func<TIn1, TIn2, TOut>> lambda = Expression.Lambda<Func<TIn1, TIn2, TOut>>(memberInitExpression, parameterExpressionList);
     68             return lambda.Compile();
     69         }
     70     }
     71     public sealed class Mapper<TIn1, TIn2, TIn3, TOut> where TOut : class where TIn1 : class where TIn2 : class where TIn3 : class
     72     {
     73         private Mapper() { }
     74         public static TOut AutoMapper(TIn1 tIn1, TIn2 tIn2, TIn3 tIn3)
     75         {
     76             return funcCache(tIn1, tIn2, tIn3);
     77         }
     78         public static TOut AutoMapper(TIn1 tIn1, TIn2 tIn2, TIn3 tIn3, Action<TOut> action)
     79         {
     80             TOut outValue = funcCache(tIn1, tIn2, tIn3);
     81             action(outValue);
     82             return outValue;
     83         }
     84         private static readonly Func<TIn1, TIn2, TIn3, TOut> funcCache = GetFunc();
     85         private static Func<TIn1, TIn2, TIn3, TOut> GetFunc()
     86         {
     87             Type[] types = new Type[] { typeof(TIn1), typeof(TIn2), typeof(TIn3) };
     88             MemberInitExpression memberInitExpression;
     89             List<ParameterExpression> parameterExpressionList;
     90             MapperExpressionCommon.GetFunc(typeof(TOut), types, out memberInitExpression, out parameterExpressionList);
     91             Expression<Func<TIn1, TIn2, TIn3, TOut>> lambda = Expression.Lambda<Func<TIn1, TIn2, TIn3, TOut>>(memberInitExpression, parameterExpressionList);
     92             return lambda.Compile();
     93         }
     94     }
     95     public sealed class Mapper<TIn1, TIn2, TIn3, TIn4, TOut> where TOut : class where TIn1 : class where TIn2 : class where TIn3 : class where TIn4 : class
     96     {
     97         private Mapper() { }
     98         public static TOut AutoMapper(TIn1 tIn1, TIn2 tIn2, TIn3 tIn3, TIn4 tIn4)
     99         {
    100             return funcCache(tIn1, tIn2, tIn3, tIn4);
    101         }
    102         public static TOut AutoMapper(TIn1 tIn1, TIn2 tIn2, TIn3 tIn3, TIn4 tIn4, Action<TOut> action)
    103         {
    104             TOut outValue = funcCache(tIn1, tIn2, tIn3, tIn4);
    105             action(outValue);
    106             return outValue;
    107         }
    108         private static readonly Func<TIn1, TIn2, TIn3, TIn4, TOut> funcCache = GetFunc();
    109         private static Func<TIn1, TIn2, TIn3, TIn4, TOut> GetFunc()
    110         {
    111             Type[] types = new Type[] { typeof(TIn1), typeof(TIn2), typeof(TIn3), typeof(TIn4) };
    112             MemberInitExpression memberInitExpression;
    113             List<ParameterExpression> parameterExpressionList;
    114             MapperExpressionCommon.GetFunc(typeof(TOut), types, out memberInitExpression, out parameterExpressionList);
    115             Expression<Func<TIn1, TIn2, TIn3, TIn4, TOut>> lambda = Expression.Lambda<Func<TIn1, TIn2, TIn3, TIn4, TOut>>(memberInitExpression, parameterExpressionList);
    116             return lambda.Compile();
    117         }
    118     }
    119     public sealed class Mapper<TIn1, TIn2, TIn3, TIn4, TIn5, TOut> where TOut : class where TIn1 : class where TIn2 : class where TIn3 : class where TIn4 : class where TIn5 : class
    120     {
    121         private Mapper() { }
    122         public static TOut AutoMapper(TIn1 tIn1, TIn2 tIn2, TIn3 tIn3, TIn4 tIn4, TIn5 tIn5)
    123         {
    124             return funcCache(tIn1, tIn2, tIn3, tIn4, tIn5);
    125         }
    126         public static TOut AutoMapper(TIn1 tIn1, TIn2 tIn2, TIn3 tIn3, TIn4 tIn4, TIn5 tIn5, Action<TOut> action)
    127         {
    128             TOut outValue = funcCache(tIn1, tIn2, tIn3, tIn4, tIn5);
    129             action(outValue);
    130             return outValue;
    131         }
    132         private static readonly Func<TIn1, TIn2, TIn3, TIn4, TIn5, TOut> funcCache = GetFunc();
    133         private static Func<TIn1, TIn2, TIn3, TIn4, TIn5, TOut> GetFunc()
    134         {
    135             Type[] types = new Type[] { typeof(TIn1), typeof(TIn2), typeof(TIn3), typeof(TIn4), typeof(TIn5) };
    136             MemberInitExpression memberInitExpression;
    137             List<ParameterExpression> parameterExpressionList;
    138             MapperExpressionCommon.GetFunc(typeof(TOut), types, out memberInitExpression, out parameterExpressionList);
    139             Expression<Func<TIn1, TIn2, TIn3, TIn4, TIn5, TOut>> lambda = Expression.Lambda<Func<TIn1, TIn2, TIn3, TIn4, TIn5, TOut>>(memberInitExpression, parameterExpressionList);
    140             return lambda.Compile();
    141         }
    142     }
    143 }
    View Code
  • 相关阅读:
    You are not late! You are not early!
    在同一个服务器(同一个IP)为不同域名绑定的免费SSL证书
    Vue.js Is Good, but Is It Better Than Angular or React?
    It was not possible to find any compatible framework version
    VS增加插件 Supercharger破解教程
    Git使用ssh key
    Disconnected: No supported authentication methods available (server sent: publickey)
    VS 2013打开.edmx文件时报类型转换异常
    asp.net MVC4 框架揭秘 读书笔记系列3
    asp.net MVC4 框架揭秘 读书笔记系列2
  • 原文地址:https://www.cnblogs.com/7tiny/p/8764548.html
Copyright © 2011-2022 走看看