ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

C#使用表达式树(LambdaExpression)动态更新类的属性值

2022-08-04 18:03:36  阅读:154  来源: 互联网

标签:Name C# LambdaExpression public student var Expression teacher 表达式


在某些业务中会遇到需要同步两个类的属性值的情况,而且有些字段是要过滤掉的。如果手动赋值则需要写很多重复的代码:

 
 public class Teacher
        {
            public Guid Id { get; set; }
            public string Name { get; set; }
            public string Age { get; set; }
        }
        public class Student
        {
            public Guid Id { get; set; }
            public string Name { get; set; }
            public string Age { get; set; }
        }
   /// <summary>
        /// 比如需要把teacher的某些属性值赋给student,而id不需要赋值
        /// </summary>
        /// <param name="student"></param>
        /// <param name="teacher"></param>
        public static void SetProperty(Student student, Teacher teacher)
        {
            if (student.Name != teacher.Name)
            {
                student.Name = teacher.Name;
            }
            if (student.Age != teacher.Age)
            {
                student.Age = teacher.Age;
            }
        }


使用反射的话性能考虑,尝试写一个扩展方法使用lambda表达式树去构建一个方法

 
public static class ObjectExtensions
    {
        /// <summary>
        /// 缓存表达式
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <typeparam name="TTarget"></typeparam>
        public static class MapperAccessor<TSource, TTarget>
        {
            private static Action<TSource, TTarget, string[]> func { get; set; }
            public static TSource Set(TSource source, TTarget target, params string[] properties)
            {
                if (func == null)
                {
                    var sourceType = typeof(TSource);
                    var targetType = typeof(TTarget);
                    BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public;
                    if (properties.Length == 0)
                    {
                        //get all properties
                        if (sourceType == targetType)
                        {
                            //如果是相同类型则获取所有属性
                            properties = sourceType.GetProperties(bindingFlags).Select(x => x.Name)
                                .ToArray();
                        }
                        else
                        {
                            //如果没有传指定的属性则默认获取同名属性
                            List<PropertyInfo> propertyInfos = new List<PropertyInfo>();
                            foreach (var property in sourceType.GetProperties(bindingFlags))
                            {//不同类型指定同名且类型相同的属性
                                var targetProperty = targetType.GetProperty(property.Name, bindingFlags);
                                if (targetProperty != null && targetProperty.PropertyType == property.PropertyType)
                                {
                                    propertyInfos.Add(property);
                                }
                            }
                            properties = propertyInfos.Select(x => x.Name).ToArray();
                        }
                    }
                    //定义lambda 3个参数
                    var s = Expression.Parameter(typeof(TSource), "s");
                    var t = Expression.Parameter(typeof(TTarget), "t");
                    var ps = Expression.Parameter(typeof(string[]), "ps");
                    //获取泛型扩展方法Contains
                    var methodInfo = typeof(Enumerable).GetMethods().FirstOrDefault(e => e.Name == "Contains" && e.GetParameters().Length == 2);
                    if (methodInfo == null)
                    {
                        // properties.Contains()
                        throw new NullReferenceException(nameof(methodInfo));
                    }
                    MethodInfo genericMethod = methodInfo.MakeGenericMethod(typeof(String));//创建泛型方法
                    List<BlockExpression> bs = new List<BlockExpression>();
                    foreach (string field in properties)
                    {
                        //获取两个类型里面的属性
                        var sourceField = Expression.Property(s, field);
                        var targetField = Expression.Property(t, field);
                        //创建一个条件表达式
                        var notEqual = Expression.NotEqual(sourceField, targetField);//sourceField!=targetField
                        var method = Expression.Call(null, genericMethod, ps, Expression.Constant(field));//ps.Contains(f);
                        //构建赋值语句
                        var ifTrue = Expression.Assign(sourceField, targetField);
                        //拼接表达式 sourceField!=targetField&&ps.Contains(f)
                        var condition = Expression.And(notEqual, Expression.IsTrue(method));
                        //判断是否相同,如果不相同则赋值
                        var expression = Expression.IfThen(condition, ifTrue);
                        bs.Add(Expression.Block(expression));
                    }
                    var lambda = Expression.Lambda<Action<TSource, TTarget, string[]>>(Expression.Block(bs), s, t, ps);
                    func = lambda.Compile();
                }
                func.Invoke(source, target, properties);
                return source;
            }
        }
        /// <summary>
        /// 通过目标类更新源类同名属性值
        /// </summary>
        /// <typeparam name="TSource">待更新的数据类型</typeparam>
        /// <typeparam name="TTarget">目标数据类型</typeparam>
        /// <param name="source">源数据</param>
        /// <param name="target">目标数据</param>
        /// <param name="properties">要变更的属性名称</param>
        /// <returns>返回源数据,更新后的</returns>
        public static TSource SetProperties<TSource, TTarget>(this TSource source, TTarget target, params string[] properties)
        {
            return MapperAccessor<TSource, TTarget>.Set(source, target, properties);
        }
    }

编写测试方法

 
 /// <summary>
        /// 比如需要把teacher的某些属性值赋给student,而id不需要赋值
        /// </summary>
        /// <param name="student"></param>
        /// <param name="teacher"></param>
        public static void SetProperty(Student student, Teacher teacher)
        {
            if (student.Name != teacher.Name)
            {
                student.Name = teacher.Name;
            }
            if (student.Age != teacher.Age)
            {
                student.Age = teacher.Age;
            }
        }
        public static void SetProperty2(Student student, Teacher teacher, params string[] properties)
        {
            var sourceType = student.GetType();
            var targetType = teacher.GetType();
            foreach (var property in properties)
            {
                var aP = sourceType.GetProperty(property);
                var bP = targetType.GetProperty(property);
                var apValue = aP.GetValue(student);
                var bpValue = bP.GetValue(teacher);
                if (apValue != bpValue)
                {
                    aP.SetValue(student, bpValue);
                }
            }
        }

        static (List<Student>, List<Teacher>) CreateData(int length)
        {
            var rd = new Random();
            (List<Student>, List<Teacher>) ret;
            ret.Item1 = new List<Student>();
            ret.Item2 = new List<Teacher>();
            for (int i = 0; i < length; i++)
            {
                Student student = new Student()
                {
                    Id = Guid.NewGuid(),
                    Name = Guid.NewGuid().ToString("N"),
                    Age = rd.Next(1, 100)
                };
                ret.Item1.Add(student);
                Teacher teacher = new Teacher()
                {
                    Id = Guid.NewGuid(),
                    Name = Guid.NewGuid().ToString("N"),
                    Age = rd.Next(1, 100)
                };
                ret.Item2.Add(teacher);
            }
            return ret;
        }
        static void Main(string[] args)
        {

            var length = 1000000;
            var data = CreateData(length);
            Stopwatch sw = new Stopwatch();
            sw.Start();
            for (int i = 0; i < length; i++)
            {
                SetProperty(data.Item1[i], data.Item2[i]);
            }
            sw.Stop();
            Console.WriteLine($"手写方法耗时:{sw.ElapsedMilliseconds}ms");
            data.Item1.Clear();
            data.Item2.Clear();
            var data2 = CreateData(length);
            sw.Restart();
            for (int i = 0; i < length; i++)
            {
                data2.Item1[i].SetProperties(data2.Item2[i], nameof(Student.Age), nameof(Student.Name));
            }
            data2.Item1.Clear();
            data2.Item2.Clear();
            sw.Stop();
            Console.WriteLine($"lambda耗时:{sw.ElapsedMilliseconds}ms");
            var data3 = CreateData(length);
            sw.Restart();
            for (int i = 0; i < length; i++)
            {
                SetProperty2(data3.Item1[i], data3.Item2[i], nameof(Student.Age), nameof(Student.Name));
            }
            sw.Stop();
            Console.WriteLine($"反射耗时:{sw.ElapsedMilliseconds}ms");
            data3.Item1.Clear();
            data3.Item2.Clear();
      
            Console.ReadKey();
        }

可以看到性能和手写方法之间的差距,如果要求比较高还是手写方法,如果字段多的话写起来是很痛苦的事。但是日常用这个足够了,而且是扩展方法,通用性很强。

  转 https://www.cnblogs.com/tchivs/p/15787353.html

标签:Name,C#,LambdaExpression,public,student,var,Expression,teacher,表达式
来源: https://www.cnblogs.com/wl-blog/p/16551462.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有