原文:整理:C#中Expression表达式的妙用

一、目的:通过示例了解C#中Expression表达式的作用,通过表达式和反射可以写出很优雅的代码和架构,也可以完成一些看似不可能完成的任务

二、示例:

1、通过表达式获取成员属性

定义模型:


  1. [Description("唯一标识")]
  2. class PersonModel
  3. {
  4. [Description("唯一标识")]
  5. public string ID { get; set; }
  6. [Description("名称")]
  7. public string Name { get; set; }
  8. [Description("值")]
  9. public double Value { get; set; }
  10. [Description("年齡")]
  11. public double Age { get; set; }
  12. [Description("收入")]
  13. public double InCome { get; set; }
  14. [Description("支出")]
  15. public double Pay { get; set; }
  16. }

定义获取模型属性和描述信息的表达式方法


  1. /// <summary>
  2. /// 通过Linq表达式获取成员属性
  3. /// </summary>
  4. /// <typeparam name="T"></typeparam>
  5. /// <param name="instance"></param>
  6. /// <param name="expression"></param>
  7. /// <returns></returns>
  8. public Tuple<string, string> GetPropertyValue<T>(T instance, Expression<Func<T, string>> expression)
  9. {
  10. MemberExpression memberExpression = expression.Body as MemberExpression;
  11. string propertyName = memberExpression.Member.Name;
  12. string attributeName = (memberExpression.Member.GetCustomAttributes(false)[0] as DescriptionAttribute).Description;
  13. var property = typeof(T).GetProperties().Where(l => l.Name == propertyName).First();
  14. return new Tuple<string, string>(attributeName, property.GetValue(instance).ToString());
  15. }

应用示例:


  1. [TestMethod]
  2. public void TestMethod1()
  3. {
  4. // Message:根据表达式获取对应属性的值
  5. PersonModel model = new PersonModel();
  6. model.ID = "1";
  7. model.Name = "王杰";
  8. model.Value = 90;
  9. model.InCome = 100;
  10. model.Pay = 200;
  11. model.Age = 33;
  12. var result = this.GetPropertyValue(model, l => l.Name);
  13. Debug.WriteLine($"显示名称:{result.Item1}-值:{result.Item2}");
  14. }

输出结果:

Description:名称 - Name:王杰

也许有人会问,真是多此一举,直接model.Name就可以了,在此示例只演示表达式的应用方法,应用表达式代替Lambda表达式的好处在于Expression是一个结构体,包含了表达式中需要的信息,如l.Name通过Expression表达式方式传入可以很方便的获取到Name这个string,对应用反射创建数据非常有用,后面介绍一个示例,同个此示例可以根据数据库表快速生成想要的报表非常方便;

2、通过表达式快速生成报表

定义应用表达式动态创建的求和报表生成方法


  1. /// <summary>
  2. /// 获取汇总求和数据
  3. /// </summary>
  4. /// <typeparam name="T"></typeparam>
  5. /// <param name="collection"></param>
  6. /// <param name="groupby"></param>
  7. /// <param name="expressions"></param>
  8. /// <returns></returns>
  9. public DataTable GetSum<T>(IQueryable<T> collection, Expression<Func<T, String>> groupby, params Expression<Func<T, double>>[] expressions)
  10. {
  11. DataTable table = new DataTable();
  12. // Message:利用表达式设置列名称
  13. MemberExpression memberExpression = groupby.Body as MemberExpression;
  14. var displayName = (memberExpression.Member.GetCustomAttributes(false)[0] as DescriptionAttribute).Description;
  15. table.Columns.Add(new DataColumn(displayName));
  16. foreach (var expression in expressions)
  17. {
  18. memberExpression = expression.Body as MemberExpression;
  19. displayName = (memberExpression.Member.GetCustomAttributes(false)[0] as DescriptionAttribute).Description;
  20. table.Columns.Add(new DataColumn(displayName));
  21. }
  22. // Message:通过表达式设置数据体
  23. var groups = collection.GroupBy(groupby);
  24. foreach (var group in groups)
  25. {
  26. // Message:设置分组列头
  27. DataRow dataRow = table.NewRow();
  28. dataRow[0] = group.Key;
  29. // Message:设置分组汇总数据
  30. for (int i = 0; i < expressions.Length; i++)
  31. {
  32. var expression = expressions[i];
  33. Func<T, double> fun = expression.Compile();
  34. dataRow[i + 1] = group.Sum(fun);
  35. }
  36. table.Rows.Add(dataRow);
  37. }
  38. return table;
  39. }

应用示例


  1. [TestMethod]
  2. public void TestMethod2()
  3. {
  4. // Message:根据表达式获取对应属性的值
  5. List<PersonModel> models = new List<PersonModel>();
  6. Random r = new Random();
  7. string[] names = { "张学友", "王杰", "刘德华", "张曼玉", "李连杰", "孙悟空" };
  8. // Message:构造测试数据
  9. for (int i = 0; i < 80; i++)
  10. {
  11. PersonModel model = new PersonModel();
  12. model.ID = i.ToString();
  13. model.Name = names[r.Next(6)];
  14. model.Value = r.Next(20, 100);
  15. model.InCome = r.Next(20, 100);
  16. model.Pay = r.Next(20, 100);
  17. model.Age = r.Next(20, 100);
  18. models.Add(model);
  19. }
  20. // Message:生成自定义报表
  21. DataTable dt = this.GetSum(models.AsQueryable(), l => l.Name, l => l.Value, l => l.Age);
  22. WriteTable(dt);
  23. }
  24. public void WriteTable(DataTable dt)
  25. {
  26. string colums = string.Empty;
  27. ;
  28. foreach (DataColumn item in dt.Columns)
  29. {
  30. colums += item.ColumnName.PadRight(5,' ') + " ";
  31. }
  32. Debug.WriteLine(colums);
  33. foreach (DataRow item in dt.Rows)
  34. {
  35. string rows = string.Empty;
  36. for (int i = 0; i < dt.Columns.Count; i++)
  37. {
  38. rows += item[i].ToString().PadRight(5, ' ') + " ";
  39. }
  40. Debug.WriteLine(rows);
  41. }
  42. }

输出结果:

名称    值     年齡    

孙悟空   1018  820   

李连杰   737   972   

刘德华   655   614   

张曼玉   1160  1000  

王杰    791   663   

张学友   517   435

可以看到

DataTable dt = this.GetSum(models.AsQueryable(), l => l.Name, l => l.Value, l => l.Age);

我们只需要传入

l => l.Name汇总分组表达式

l => l.Value按Value属性求和

l => l.Age按Age属性求和就会生成报表

同理输入

DataTable dt = this.GetSum(models.AsQueryable(), l => l.Name, l => l.Value, l => l.Age, l => l.InCome, l => l.Pay);

可以汇总到如下报表

名称    值     年齡    收入    支出    

张曼玉   965   1090  1025  870   

孙悟空   613   534   575   579   

刘德华   638   921   856   823   

李连杰   906   861   996   794   

张学友   811   731   848   881   

王杰    501   651   572   734

同理可以按其他字段分组

DataTable dt = this.GetSum(models.AsQueryable(), l => l.ID, l => l.Value, l => l.Age, l => l.InCome, l => l.Pay);

可以汇众到如下报表

唯一标识  值     年齡    收入    支出    

0     34    38    68    61    

1     45    95    45    62    

2     81    68    92    82    

3     31    61    94    66    

4     90    96    58    90    

5     40    46    71    41

如果不应用Expression表达式直接用Lambda表达式同样可以生成报表数据部分,但如果想通过一条语句完成抬头道数据的生成则离不开Expression的帮助了

l => l.Value Lambda表达式会自动转换成Expression<Func<T, double>>

Expression<Func<T, double>>方法通过Func<T, double> fun = expression.Compile();可以转换成Lambda表达式

同时也会发现,[Description("唯一标识")]特性也可以定义一个泛型方法去输入

修改如下方法,增加传入转换抬头的特性参数


  1. /// <summary>
  2. /// 获取汇总求和数据
  3. /// </summary>
  4. /// <typeparam name="T"></typeparam>
  5. /// <param name="collection"></param>
  6. /// <param name="groupby"></param>
  7. /// <param name="expressions"></param>
  8. /// <returns></returns>
  9. public DataTable GetSum<TModel,TAttr>(IQueryable<TModel> collection,Func<TAttr,string> toHeader, Expression<Func<TModel, String>> groupby, params Expression<Func<TModel, double>>[] expressions)
  10. {
  11. DataTable table = new DataTable();
  12. // Message:利用表达式设置列名称
  13. MemberExpression memberExpression = groupby.Body as MemberExpression;
  14. var displayName = toHeader((TAttr)memberExpression.Member.GetCustomAttributes(typeof(TAttr),false).First());
  15. table.Columns.Add(new DataColumn(displayName));
  16. foreach (var expression in expressions)
  17. {
  18. memberExpression = expression.Body as MemberExpression;
  19. displayName = toHeader((TAttr)memberExpression.Member.GetCustomAttributes(typeof(TAttr), false).First());
  20. table.Columns.Add(new DataColumn(displayName));
  21. }
  22. // Message:通过表达式设置数据体
  23. var groups = collection.GroupBy(groupby);
  24. foreach (var group in groups)
  25. {
  26. // Message:设置分组列头
  27. DataRow dataRow = table.NewRow();
  28. dataRow[0] = group.Key;
  29. // Message:设置分组汇总数据
  30. for (int i = 0; i < expressions.Length; i++)
  31. {
  32. var expression = expressions[i];
  33. Func<TModel, double> fun = expression.Compile();
  34. dataRow[i + 1] = group.Sum(fun);
  35. }
  36. table.Rows.Add(dataRow);
  37. }
  38. return table;
  39. }

实体修改如下


  1. [Description("唯一标识")]
  2. class PersonModel
  3. {
  4. [Description("唯一标识")]
  5. [DesignerCategory("分组一")]
  6. public string ID { get; set; }
  7. [Description("名称")]
  8. [DesignerCategory("分组二")]
  9. public string Name { get; set; }
  10. [Description("值")]
  11. [DesignerCategory("分组三")]
  12. public double Value { get; set; }
  13. [Description("年齡")]
  14. [DesignerCategory("分组四")]
  15. public double Age { get; set; }
  16. [Description("收入")]
  17. [DesignerCategory("分组五")]
  18. public double InCome { get; set; }
  19. [Description("支出")]
  20. [DesignerCategory("分组六")]
  21. public double Pay { get; set; }
  22. }

测试代码:


  1. [TestMethod]
  2. public void TestMethod3()
  3. {
  4. // Message:根据表达式获取对应属性的值
  5. List<PersonModel> models = new List<PersonModel>();
  6. Random r = new Random();
  7. string[] names = { "张学友", "王杰", "刘德华", "张曼玉", "李连杰", "孙悟空" };
  8. // Message:构造测试数据
  9. for (int i = 0; i < 80; i++)
  10. {
  11. PersonModel model = new PersonModel();
  12. model.ID = i.ToString();
  13. model.Name = names[r.Next(6)];
  14. model.Value = r.Next(20, 100);
  15. model.InCome = r.Next(20, 100);
  16. model.Pay = r.Next(20, 100);
  17. model.Age = r.Next(20, 100);
  18. models.Add(model);
  19. }
  20. Func<DesignerCategoryAttribute, string> toHeader = l => l.Category;
  21. // Message:生成自定义报表
  22. DataTable dt = this.GetSum(models.AsQueryable(), toHeader, l => l.Name, l => l.Value);
  23. WriteTable(dt);
  24. }

输入出结果如下:

分组二   分组三   

张学友   723   

张曼玉   1083  

孙悟空   481   

王杰    822   

李连杰   664   

刘德华   790

以上简单介绍Expression表达式在自动生成报表中的应用,其中还有许多可扩展部分后续会扩展,如Sum、Max、Min等其他聚合函数的封装,下面演示针对具体聚合函数的整合函数

3、聚合函数表达式封装

针对2中GetSum方法,同样可以应用表达式对参数进行处理,可以针对传入的聚合函数的不同分别出报表

封装方法如下:


  1. /// <summary>
  2. /// 获取汇总求和数据
  3. /// </summary>
  4. /// <typeparam name="T"></typeparam>
  5. /// <param name="collection"></param>
  6. /// <param name="groupby"></param>
  7. /// <param name="expressions"></param>
  8. /// <returns></returns>
  9. DataTable GetReport<T>(IQueryable<T> collection, Expression<Func<T, String>> groupby, params Expression<Func<IQueryable<T>, double>>[] expressions)
  10. {
  11. DataTable table = new DataTable();
  12. // Message:利用表达式设置列名称
  13. MemberExpression memberExpression = groupby.Body as MemberExpression;
  14. var displayName = (memberExpression.Member.GetCustomAttributes(false)[0] as DescriptionAttribute).Description;
  15. table.Columns.Add(new DataColumn(displayName));
  16. foreach (var expression in expressions)
  17. {
  18. MethodCallExpression dynamicExpression = expression.Body as MethodCallExpression;
  19. string groupName = dynamicExpression.Method.Name;
  20. UnaryExpression unaryexpression = dynamicExpression.Arguments[1] as UnaryExpression;
  21. LambdaExpression LambdaExpression = unaryexpression.Operand as LambdaExpression;
  22. memberExpression = LambdaExpression.Body as MemberExpression;
  23. displayName = (memberExpression.Member.GetCustomAttributes(false)[0] as DescriptionAttribute).Description;
  24. table.Columns.Add(new DataColumn(displayName+$"({groupName})"));
  25. }
  26. // Message:通过表达式设置数据体
  27. var groups = collection.GroupBy(groupby);
  28. foreach (var group in groups)
  29. {
  30. // Message:设置分组列头
  31. DataRow dataRow = table.NewRow();
  32. dataRow[0] = group.Key;
  33. // Message:设置分组汇总数据
  34. for (int i = 0; i < expressions.Length; i++)
  35. {
  36. var expression = expressions[i];
  37. Func<IQueryable<T>, double> fun = expression.Compile();
  38. dataRow[i + 1] = fun(group.AsQueryable());
  39. }
  40. table.Rows.Add(dataRow);
  41. }
  42. return table;
  43. }

运行测试代码如下:


  1. [TestMethod]
  2. public void TestMethod6()
  3. {
  4. // Message:根据表达式获取对应属性的值
  5. List<PersonModel> models = new List<PersonModel>();
  6. Random r = new Random();
  7. string[] names = { "张学友", "王杰", "刘德华", "张曼玉", "李连杰", "孙悟空" };
  8. // Message:构造测试数据
  9. for (int i = 0; i < 80; i++)
  10. {
  11. PersonModel model = new PersonModel();
  12. model.ID = i.ToString();
  13. model.Name = names[r.Next(6)];
  14. model.Value = r.Next(20, 100);
  15. model.InCome = r.Next(20, 100);
  16. model.Pay = r.Next(20, 100);
  17. model.Age = r.Next(20, 100);
  18. models.Add(model);
  19. }
  20. // Message:生成自定义报表
  21. DataTable dt = this.GetReport(models.AsQueryable(), l => l.Name, l => l.Max(k=>k.InCome), l => l.Min(k => k.InCome), l => l.Sum(k => k.InCome));
  22. WriteTable(dt);
  23. }

运行结果如下:

名称    收入(Max) 收入(Min) 收入(Sum) 

王杰    85    22    619   

张曼玉   98    24    917   

李连杰   99    35    917   

孙悟空   96    20    671   

刘德华   95    20    957   

张学友   98    22    814

由以上结果可以看到

DataTable dt = this.GetReport(models.AsQueryable(), l => l.Name, l => l.Max(k=>k.InCome), l => l.Min(k => k.InCome), l => l.Sum(k => k.InCome));

我们传递了不同的汇总表达式,则会生成对应表达式的报表,包括抬头和数据表,有了上面的方法,我们可以更灵活、更简单的去配置出我们想要的报表,生成报表可能只要一行代码

Expression表达式的在构建时的其他优势在后面也会不断发掘

(未完待续)

最新文章

  1. CORS详解
  2. AngularJS基础知识2
  3. SVN迁项目
  4. 关于token的杂记
  5. netfilter分析
  6. [itint5]两数积全为1
  7. 1uboot移植要点[原创☆☆]
  8. 9个最新的手机/移动设备jQuery插件
  9. Mysql 关键字-保留字(转帖)
  10. [原创]抢先DriverStudio夺取机器控制权(下篇)
  11. 使用 Windows Media Center 远程控制
  12. JavaScript之包装对象
  13. H264常见术语名称
  14. JavaScript基本知识点整理(超实用)
  15. PCA算法数学原理及实现
  16. Oracle中 to_date和to_char用法
  17. jq 获取下一个兄弟原素 下拉箭头旋转
  18. error: cannot lock ref &#39;refs/remotes/origin/master&#39;: unable to resolve reference &#39;refs/remotes/origin/master&#39;: reference broken...
  19. intellij idea 显示打开文件路径按钮
  20. C++二进制字符串转Base64字符串 Base64字符串转二进制字符串

热门文章

  1. List转换数组 数组转换List
  2. Mysql 主从一致校验工具------Maatkit工具包
  3. mysql 连接数用完,root也无法登陆的处理方法
  4. Ninja使用Visual Studio(cl.exe)构建
  5. mongo连接不上Timed out after 30000
  6. 使用Deployment控制器创建Pods并使Service发布到外网可访问
  7. 04、rpm+yum+tar解压
  8. 一探istio-proxy(envoy)容器里的秘密
  9. RabbitMQ六种队列模式-简单队列模式
  10. java读取HDFS压缩文件乱码