【学习笔记】Expression表达式目录树

Expression表达式目录树:一个能拼装能解析的数据结构,语法树。

下面我们来看个简单的例子:

需求:一个Web应用通过前端收集用户的输入成为Dto,然后将Dto转换成领域模型并持久化到数据库中。也就是说在实际的软件开发项目中,我们的“业务逻辑”常常需要我们对同样的数据进行各种变换。

解决这个问题我们的常规做法是:使用AutoMapper来完成Dto与Model之间的实体映射。

此处引发一个思考:如果让我们自己来完成实体映射,那么我们有哪些解决思路呢?

准备测试用的实体和Dto:

/// <summary>
/// 实体
/// </summary>
public class People
{
    public int Age { get; set; }

    public string Name { get; set; }

    public int Id;
}

/// <summary>
/// Dto
/// </summary>
public class DtoPeople
{
    public int Age { get; set; }

    public string Name { get; set; }

    public int Id;
}

第1种解决思路:硬编码

People people = new People()
{
    Id = 1,
    Name = "测试",
    Age = 22
};

for (int i = 0; i < 1_000_000; i++)
{
    DtoPeople dtoPeople = new DtoPeople()
    {
        Id = people.Id,
        Name = people.Name,
        Age = people.Age
    };
}

第2种解决思路:反射

/// <summary>
/// 反射映射
/// </summary>
public class ReflectionMapper
{
    /// <summary>
    /// 实体转换
    /// </summary>
    /// <typeparam name="T">传入类型</typeparam>
    /// <typeparam name="TResult">返回值类型</typeparam>
    /// <param name="tIn">传入参数</param>
    /// <returns>转换好的实体</returns>
    public static TResult Trans<T, TResult>(T tIn)
    {
        TResult tOut = Activator.CreateInstance<TResult>();
        foreach (var itemOut in tOut.GetType().GetProperties())
        {
            var propIn = tIn.GetType().GetProperty(itemOut.Name);
            itemOut.SetValue(tOut, propIn.GetValue(tIn));
        }

        foreach (var itemOut in tOut.GetType().GetFields())
        {
            var fieldIn = tIn.GetType().GetField(itemOut.Name);
            itemOut.SetValue(tOut, fieldIn.GetValue(tIn));
        }

        return tOut;
    }
}

第3种解决思路:序列化反序列化

/// <summary>
/// 使用第三方序列化反序列化工具
/// </summary>
public class SerializeMapper
{
    /// <summary>
    /// 实体转换
    /// </summary>
    public static TResult Trans<T, TResult>(T tIn)
    {
        return JsonConvert.DeserializeObject<TResult>(JsonConvert.SerializeObject(tIn));
    }
}

第4种解决思路:表达式目录树 + 字典缓存

/// <summary>
/// 生成表达式目录树 字典缓存
/// </summary>
public class ExpressionMapper
{
    /// <summary>
    /// 字典缓存--hash分布
    /// </summary>
    private static Dictionary<string, object> _dic = new Dictionary<string, object>();

    /// <summary>
    /// 实体转换
    /// </summary>
    public static TResult Trans<T, TResult>(T tIn)
    {
        string key = string.Format("funckey_{0}_{1}", typeof(T).FullName, typeof(TResult).FullName);
        if (!_dic.ContainsKey(key))
        {
            ParameterExpression parameterExpression = Expression.Parameter(typeof(T), "p");
            List<MemberBinding> memberBindingList = new List<MemberBinding>();
            foreach (var item in typeof(TResult).GetProperties())
            {
                MemberExpression property = Expression.Property(parameterExpression, typeof(T).GetProperty(item.Name));
                MemberBinding memberBinding = Expression.Bind(item, property);
                memberBindingList.Add(memberBinding);
            }
            foreach (var item in typeof(TResult).GetFields())
            {
                MemberExpression property = Expression.Field(parameterExpression, typeof(T).GetField(item.Name));
                MemberBinding memberBinding = Expression.Bind(item, property);
                memberBindingList.Add(memberBinding);
            }
            MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(TResult)), memberBindingList.ToArray());
            Expression<Func<T, TResult>> lambda = Expression.Lambda<Func<T, TResult>>(memberInitExpression, new ParameterExpression[]
            {
                parameterExpression
            });
            Func<T, TResult> func = lambda.Compile(); //调用Compile方法将表达式转换成委托
            _dic[key] = func; //拼装是一次性的
        }

        return ((Func<T, TResult>)_dic[key]).Invoke(tIn);
    }
}

第5种解决思路:表达式目录树 + 泛型缓存(泛型缓存特点:为不同类型的组合去缓存一个结果。)

/// <summary>
/// 生成表达式目录树  泛型缓存
/// </summary>
/// <typeparam name="T">传入参数类型</typeparam>
/// <typeparam name="TResult">返回值类型</typeparam>
public class ExpressionGenericMapper<T, TResult>
{
    /// <summary>
    /// 泛型缓存
    /// </summary>
    private static Func<T, TResult> _func = null;

    /// <summary>
    /// 静态构造函数(只会被调用一次)
    /// </summary>
    static ExpressionGenericMapper()
    {
        ParameterExpression parameterExpression = Expression.Parameter(typeof(T), "p");
        List<MemberBinding> memberBindingList = new List<MemberBinding>();
        foreach (var item in typeof(TResult).GetProperties())
        {
            MemberExpression property = Expression.Property(parameterExpression, typeof(T).GetProperty(item.Name));
            MemberBinding memberBinding = Expression.Bind(item, property);
            memberBindingList.Add(memberBinding);
        }
        foreach (var item in typeof(TResult).GetFields())
        {
            MemberExpression property = Expression.Field(parameterExpression, typeof(T).GetField(item.Name));
            MemberBinding memberBinding = Expression.Bind(item, property);
            memberBindingList.Add(memberBinding);
        }
        MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(TResult)), memberBindingList.ToArray());
        Expression<Func<T, TResult>> lambda = Expression.Lambda<Func<T, TResult>>(memberInitExpression, new ParameterExpression[]
        {
            parameterExpression
        });
        _func = lambda.Compile();//拼装是一次性的
    }

    /// <summary>
    /// 实体转换
    /// </summary>
    public static TResult Trans(T t)
    {
        return _func(t);
    }
}

下面我们来测试下这5种方案的性能:

/// <summary>
/// 性能测试
/// </summary>
public static void MapperTest()
{
    People people = new People()
    {
        Id = 1,
        Name = "测试",
        Age = 22
    };

    long common = 0;
    long generic = 0;
    long cache = 0;
    long reflection = 0;
    long serialize = 0;
    {
        Stopwatch watch = new Stopwatch();
        watch.Start();
        for (int i = 0; i < 1_000_000; i++)
        {
            DtoPeople dtoPeople = new DtoPeople()
            {
                Id = people.Id,
                Name = people.Name,
                Age = people.Age
            };
        }
        watch.Stop();
        common = watch.ElapsedMilliseconds;
    }
    {
        Stopwatch watch = new Stopwatch();
        watch.Start();
        for (int i = 0; i < 1_000_000; i++)
        {
            DtoPeople dtoPeople = ReflectionMapper.Trans<People, DtoPeople>(people);
        }
        watch.Stop();
        reflection = watch.ElapsedMilliseconds;
    }
    {
        Stopwatch watch = new Stopwatch();
        watch.Start();
        for (int i = 0; i < 1_000_000; i++)
        {
            DtoPeople dtoPeople = SerializeMapper.Trans<People, DtoPeople>(people);
        }
        watch.Stop();
        serialize = watch.ElapsedMilliseconds;
    }
    {
        Stopwatch watch = new Stopwatch();
        watch.Start();
        for (int i = 0; i < 1_000_000; i++)
        {
            DtoPeople dtoPeople = ExpressionMapper.Trans<People, DtoPeople>(people);
        }
        watch.Stop();
        cache = watch.ElapsedMilliseconds;
    }
    {
        Stopwatch watch = new Stopwatch();
        watch.Start();
        for (int i = 0; i < 1_000_000; i++)
        {
            DtoPeople dtoPeople = ExpressionGenericMapper<People, DtoPeople>.Trans(people);
        }
        watch.Stop();
        generic = watch.ElapsedMilliseconds;
    }

    Console.WriteLine($"common = { common} ms");
    Console.WriteLine($"reflection = { reflection} ms");
    Console.WriteLine($"serialize = { serialize} ms");
    Console.WriteLine($"cache = { cache} ms");
    Console.WriteLine($"generic = { generic} ms"); //性能比AutoMapper还要高
}

来看下运行结果:

 

 从运行结果可以看出硬编码的性能是最高的,其次就是我们的表达式目录树 + 泛型缓存,这2个的性能几乎是同一个数量级的。

 小结:

  1、既需要考虑动态(通用),又要保证性能(硬编码)---动态生成硬编码---表达式目录树拼装(动态生成委托)(得到的就是硬编码)。

  2、如果使用反射来实现某个功能时性能不高,可以考虑使用表达式目录树拼装来实现这个功能(动态生成委托)。

猜你喜欢

转载自www.cnblogs.com/xyh9039/p/12748983.html