Util應(yīng)用程序框架公共操作類(十二):Lambda表達(dá)式公共操作類(三)
今天在開發(fā)一個簡單查詢時,發(fā)現(xiàn)我的Lambda操作類的GetValue方法無法正確獲取枚舉類型值,以至查詢結(jié)果錯誤。
我增加了幾個單元測試來捕獲錯誤,代碼如下。
/// <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操作類,準(zhǔn)備修改GetValue方法,代碼見Util應(yīng)用程序框架公共操作類(八):Lambda表達(dá)式公共操作類(二)。
面對GetValue雜亂無章的代碼,我頓時感覺無法下手,必須徹底重構(gòu)它。
我之前也看過一些Lambda表達(dá)式解析的代碼和文章,基本都是使用NodeType來進(jìn)行判斷。我一直沒有使用這種方式,是因為NodeType數(shù)量龐大,并且多種NodeType可能轉(zhuǎn)換為同一種Expression類型。我當(dāng)時認(rèn)為用switch判斷NodeType工作量太大,所以直接采用As轉(zhuǎn)換為特定表達(dá)式,再判斷是否空值。
我把這種山寨方法稱為瞎貓碰到死耗子,主要依靠單元測試來捕獲需求,通過斷點調(diào)試,我可以知道轉(zhuǎn)換為哪種特定表達(dá)式。這種方法在前期看上去貌似很有效,比判斷NodeType的代碼要少,但由于使用表達(dá)式的方式千差萬別,負(fù)擔(dān)越來越重,以至無法維護(hù)了。
為了徹底重構(gòu)GetValue方法,我需要補(bǔ)充一點表達(dá)式解析的知識,我打開開源框架linq2db,仔細(xì)觀察他是如何解析的。終于看出點眉目,依靠NodeType進(jìn)行遞歸判斷。
我以前只知道使用NodeType進(jìn)行判斷,但不知道應(yīng)該采用遞歸的方式,真是知其然不知其所以然。
我對GetValue進(jìn)行了重構(gòu),代碼如下。
/// <summary>
/// 獲取值,范例:t => t.Name == "A",返回 A
/// </summary>
/// <param name="expression">表達(dá)式,范例: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>
/// 獲取屬性表達(dá)式的值
/// </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>
/// 獲取常量表達(dá)式的值
/// </summary>
private static object GetConstantExpressionValue( Expression expression ) {
var constantExpression = (ConstantExpression)expression;
return constantExpression.Value;
}
運行了全部測試,全部通過,說明沒有影響之前的功能。這正是自動化回歸測試的威力,如果沒有單元測試,我哪里敢重構(gòu)這些代碼呢。另外,修改Bug采用TDD的方式,能夠一次修復(fù),永絕后患,值得你擁有。
同時,我還重構(gòu)了其它類似的代碼,就不再貼出,下次我發(fā)放源碼時,有興趣可以看看。
.Net應(yīng)用程序框架交流QQ群: 386092459,歡迎有興趣的朋友加入討論。
謝謝大家的持續(xù)關(guān)注,我的博客地址:http://www.rzrgm.cn/xiadao521/

浙公網(wǎng)安備 33010602011771號