今天在开发一个简单查询时,发现我的Lambda操作类的GetValue方法无法正确获取枚举类型值,以至查询结果错误。

  我增加了几个单元测试来捕获错误,代码如下。

     /// <summary>
/// 测试值为枚举
/// </summary>
[TestMethod]
public void TestGetValue_Enum() {
var test1 = new Test1();
test1.NullableEnumValue = LogType.Error; //属性为枚举,值为枚举
Expression<Func<Test1, bool>> expression = test => test.EnumValue == LogType.Debug;
Assert.AreEqual( LogType.Debug.Value(), Lambda.GetValue( expression ) ); //属性为枚举,值为可空枚举
expression = test => test.EnumValue == test1.NullableEnumValue;
Assert.AreEqual( LogType.Error, Lambda.GetValue( expression ) ); //属性为可空枚举,值为枚举
expression = test => test.NullableEnumValue == LogType.Debug;
Assert.AreEqual( LogType.Debug, Lambda.GetValue( expression ) ); //属性为可空枚举,值为可空枚举
expression = test => test.NullableEnumValue == test1.NullableEnumValue;
Assert.AreEqual( LogType.Error, Lambda.GetValue( expression ) ); //属性为可空枚举,值为null
test1.NullableEnumValue = null;
expression = test => test.NullableEnumValue == test1.NullableEnumValue;
Assert.AreEqual( null, Lambda.GetValue( expression ) );
}

  单元测试成功捕获了Bug,我打开Lambda操作类,准备修改GetValue方法,代码见Util应用程序框架公共操作类(八):Lambda表达式公共操作类(二)

  面对GetValue杂乱无章的代码,我顿时感觉无法下手,必须彻底重构它。

  我之前也看过一些Lambda表达式解析的代码和文章,基本都是使用NodeType来进行判断。我一直没有使用这种方式,是因为NodeType数量庞大,并且多种NodeType可能转换为同一种Expression类型。我当时认为用switch判断NodeType工作量太大,所以直接采用As转换为特定表达式,再判断是否空值。

  我把这种山寨方法称为瞎猫碰到死耗子,主要依靠单元测试来捕获需求,通过断点调试,我可以知道转换为哪种特定表达式。这种方法在前期看上去貌似很有效,比判断NodeType的代码要少,但由于使用表达式的方式千差万别,负担越来越重,以至无法维护了。

  为了彻底重构GetValue方法,我需要补充一点表达式解析的知识,我打开开源框架linq2db,仔细观察他是如何解析的。终于看出点眉目,依靠NodeType进行递归判断。

  我以前只知道使用NodeType进行判断,但不知道应该采用递归的方式,真是知其然不知其所以然。

  我对GetValue进行了重构,代码如下。

     /// <summary>
/// 获取值,范例:t => t.Name == "A",返回 A
/// </summary>
/// <param name="expression">表达式,范例:t => t.Name == "A"</param>
public static object GetValue( Expression expression ) {
if ( expression == null )
return null;
switch ( expression.NodeType ) {
case ExpressionType.Lambda:
return GetValue( ( (LambdaExpression)expression ).Body );
case ExpressionType.Convert:
return GetValue( ( (UnaryExpression)expression ).Operand );
case ExpressionType.Equal:
case ExpressionType.NotEqual:
case ExpressionType.GreaterThan:
case ExpressionType.LessThan:
case ExpressionType.GreaterThanOrEqual:
case ExpressionType.LessThanOrEqual:
return GetValue( ( (BinaryExpression)expression ).Right );
case ExpressionType.Call:
return GetValue( ( (MethodCallExpression)expression ).Arguments.FirstOrDefault() );
case ExpressionType.MemberAccess:
return GetMemberValue( (MemberExpression)expression );
case ExpressionType.Constant:
return GetConstantExpressionValue( expression );
}
return null;
} /// <summary>
/// 获取属性表达式的值
/// </summary>
private static object GetMemberValue( MemberExpression expression ) {
if ( expression == null )
return null;
var field = expression.Member as FieldInfo;
if ( field != null ) {
var constValue = GetConstantExpressionValue( expression.Expression );
return field.GetValue( constValue );
}
var property = expression.Member as PropertyInfo;
if ( property == null )
return null;
var value = GetMemberValue( expression.Expression as MemberExpression );
return property.GetValue( value );
} /// <summary>
/// 获取常量表达式的值
/// </summary>
private static object GetConstantExpressionValue( Expression expression ) {
var constantExpression = (ConstantExpression)expression;
return constantExpression.Value;
}

  运行了全部测试,全部通过,说明没有影响之前的功能。这正是自动化回归测试的威力,如果没有单元测试,我哪里敢重构这些代码呢。另外,修改Bug采用TDD的方式,能够一次修复,永绝后患,值得你拥有。

  同时,我还重构了其它类似的代码,就不再贴出,下次我发放源码时,有兴趣可以看看。

  .Net应用程序框架交流QQ群: 386092459,欢迎有兴趣的朋友加入讨论。

  谢谢大家的持续关注,我的博客地址:http://www.cnblogs.com/xiadao521/

Util应用程序框架公共操作类(十二):Lambda表达式公共操作类(三)的更多相关文章

  1. Util应用程序框架公共操作类(九):Lambda表达式扩展

    上一篇对Lambda表达式公共操作类进行了一些增强,本篇使用扩展方法对Lambda表达式进行扩展. 修改Util项目的Extensions.Expression.cs文件,代码如下. using Sy ...

  2. Util应用程序框架公共操作类(八):Lambda表达式公共操作类(二)

    前面介绍了查询的基础扩展,下面准备给大家介绍一些有用的查询封装手法,比如对日期范围查询,数值范围查询的封装等,为了支持这些功能,需要增强公共操作类. Lambda表达式公共操作类,我在前面已经简单介绍 ...

  3. Util应用程序框架公共操作类(七):Lambda表达式公共操作类

    前一篇扩展了两个常用验证方法,本文将封装两个Lambda表达式操作,用来为下一篇的查询扩展服务. Lambda表达式是一种简洁的匿名函数语法,可以用它将方法作为委托参数传递.在Linq中,大量使用La ...

  4. Util应用程序框架公共操作类(六):验证扩展

    前面介绍了仓储的基本操作,下面准备开始扩展查询,在扩展查询之前,首先要增加两个公共操作类,一个是经常要用到的验证方法,另一个是Lambda表达式的操作类. 很多时候,我们会判断一个对象是否为null, ...

  5. Util应用程序框架公共操作类(四):验证公共操作类

    为了能够验证领域实体,需要一个验证公共操作类来提供支持.由于我将使用企业库(Enterprise Library)的验证组件来完成这项任务,所以本文也将演示对第三方框架的封装要点. .Net提供了一个 ...

  6. Util应用程序框架公共操作类

    随笔分类 - Util应用程序框架公共操作类 Util应用程序框架公共操作类 Util应用程序框架公共操作类(五):异常公共操作类 摘要: 任何系统都需要处理错误,本文介绍的异常公共操作类,用于对业务 ...

  7. 第十二章 Python文件操作【转】

    12.1 open() open()函数作用是打开文件,返回一个文件对象. 用法格式:open(name[, mode[, buffering[,encoding]]]) -> file obj ...

  8. Effective Java 第三版——42.lambda表达式优于匿名类

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  9. Util应用程序框架公共操作类(十):可空值类型扩展

    当你使用可空的值类型时,你会发现取值很不方便,比如Guid? obj,你要从obj中获取值,可以使用Value属性obj. Value,但obj可能为null,这时候就会抛出一个异常. 可空值类型提供 ...

随机推荐

  1. OC基础--构造方法 id类型

    new方法实现原理: new做了三件事情 1.开辟存储空间  + alloc 方法 2.初始化所有的属性(成员变量) - init 方法 3.返回对象的地址 [Person new]; == [[Pe ...

  2. The user specified as a definer (&#39;root&#39;@&#39;%&#39;) does not exist

    The user specified as a definer ('root'@'%') does not exist 此种报错主要是针对访问视图文件引起的(没有权限) 解决方法: 2.进入mysql ...

  3. iOS开发网络篇—监测网络状态(转)

    文章转载自:http://www.cnblogs.com/wendingding/p/3950114.html iOS开发网络篇—监测网络状态 一.说明 在网络应用中,需要对用户设备的网络状态进行实时 ...

  4. Spring学习笔记之 Spring IOC容器(二) 之注入参数值,自动组件扫描方式,控制Bean实例化方式,使用注解方式

     本节主要内容:    1. 给MessageBean注入参数值    2. 测试Spring自动组件扫描方式    3. 如何控制ExampleBean实例化方式    4. 使用注解方式重构Jdb ...

  5. 【HDOJ】3601 Coach Yehr’s punishment

    RMQ+dp+二分.最好还是离散化一下再处理,通过dp求得每个位置的上一次出现的位置pre数组,从而求得不重复的长度len.然后RMQ可以预处理区间的最大值,pre是个单调非递减数列.每次查询时,二分 ...

  6. Python 技巧

    1.根据路径导入模块 如果想引用指定路径下的某个模块,则需要使用sys.path.append("module_directory") 来把这个路径添加到sys下,这就涉及到Pyt ...

  7. Struts2实现国际化

    public class I18nAction extends ActionSupport { private static final long serialVersionUID = -693330 ...

  8. 需求分析---NABCD

    N(Need,需求) 我们的产品未来天气,是为了解决不爱看天气预报的群众开发一款类似备忘录式的天气预报软件.很多人认为今天天气很好,明天肯定不会差,但是风云忽变,可能明天就降大雨,所以就忽略了带伞, ...

  9. Android为TV端助力之解决setOnItemSelectedListener一进来就自动执行一次的问题

    我们经常会遇到listview或者其他view设置setOnItemSelectedListener监听时,一加载界面,setOnItemSelectedListener监听就会自动执行一遍,导致你第 ...

  10. 20155207 《网络对抗》exp4 恶意代码分析 学习总结

    20155207 <网络对抗> 恶意代码分析 学习总结 实践目标 1.是监控你自己系统的运行状态,看有没有可疑的程序在运行. 2.是分析一个恶意软件,就分析Exp2或Exp3中生成后门软件 ...