今天在开发一个简单查询时,发现个人Lambda操做类的GetValue方法没法正确获取枚举类型值,以致查询结果错误。html
我增长了几个单元测试来捕获错误,代码以下。express
/// <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的代码要少,但因为使用表达式的方式千差万别,负担愈来愈重,以致没法维护了。spa
为了完全重构GetValue方法,我须要补充一点表达式解析的知识,我打开开源框架linq2db,仔细观察他是如何解析的。终于看出点眉目,依靠NodeType进行递归判断。调试
我之前只知道使用NodeType进行判断,但不知道应该采用递归的方式,真是知其然不知其因此然。code
我对GetValue进行了重构,代码以下。htm
/// <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的方式,可以一次修复,永绝后患,值得你拥有。blog
同时,我还重构了其它相似的代码,就再也不贴出,下次我发放源码时,有兴趣能够看看。
.Net应用程序框架交流QQ群: 386092459,欢迎有兴趣的朋友加入讨论。
谢谢你们的持续关注,个人博客地址:http://www.cnblogs.com/xiadao521/