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

  今天在开发一个简单查询时,发现我的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/

posted @ 2015-01-24 02:22  何镇汐  阅读(...)  评论(...编辑  收藏