需求描述
數據列表如List<Customer> 加載到DataGridView后,如果記錄比較多可能需要對其進行二次過濾,即客戶端過濾
過濾條件做成可由用戶設置的,如下圖:
在數據源是DataTable時,使用DataView的RowFilter可以輕松按用戶的配置拼接出過濾表達式字符串來,
設置RowFilter就可以實現過濾效果,但是當數據源是List<T>這樣由EF,Linq to sql 等框架返回的集合時要實現上面的功能就需要費點力氣了。
問題分析:
首先參考上面的截圖,用戶設置好過濾條件后會形成:" (工號 = 222 And 部門=人力) Or 性別=女" 這樣的過濾表達式,可以表示成(Exp1 And Exp2) Or Exp3 這樣的形式.針對"工號=222"這樣的Exp求值我們會轉變成針對Employe實體的EmpId屬性是否等於222的判斷(Employe.EmpId==222),這個可以通過反射方式來實現,將多個Exp求值的結果通過And或Or連接並運算得出最終結果,True表示這一行(Employe)符合.
不過考慮Exp1 Or Exp2 Or Exp3 這樣的條件,如果第一個Exp1是True的話結果必定是True,這個時候還去計算Exp2,Exp3是完全多余的,如果List集合有幾萬條記錄(當然超過幾千行的列表對用戶來說是沒有多少意義的,一般人不會看那么多行,這個時候應該想想過濾條件設置是否合理)那么針對列表的每個實體的每個屬性(字段)使用反射的方式計算一遍Exp將是一個比較大的開銷,好在And與Or跟算術操作符(+,-,*,/)有所不同,And運算時只要兩個操作數中有一個是False就沒必要計算另外一個操作數(這里的是Exp)而Or在一個操作數是True時就可以忽略另一個操作數。不過當所的Exp都是false時針對上面"Exp1 Or Exp2 Or Exp3"這樣的表達式計算每個Exp是不可避免的
到這里我們可以看到該問題的本質就是表達式求值,而操作符只限And與Or兩個二元操作符,最后結果是True或False.
設計實現:
首先我們將用戶設置的過濾表達式轉變成逆波蘭式(后綴表達式),接着傳入每個要判斷的實體,使用后綴表達式求出該實體是否符合過濾條件,
當然我們也可以將后綴表達式構建成Expression樹,接着將該Expression編譯成動態方法(委托),使用該委托對每個實體做出判斷,下面的代碼給出了這兩種實現,
經過測試發現兩種方法速度區別不大。
自己對逆波蘭式求值時需要下面的判定表,如果構建Expression樹則Expression.AndAlso或Expression.OrElse會自己判斷是否對兩個操作數都進行計算(參考下面的代碼)
代碼:

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Linq.Expressions; using System.Reflection; namespace FIStudio.WinUI.Core { public class ExpParser { public ExpParser(List<Elem> midfixList) { this.MidfixList = midfixList; } public List<Elem> PostfixList { get; private set; } public List<Elem> MidfixList { get; private set; } private Stack<Elem> CalcStack = new Stack<Elem>(); private void GuardMidfixListExist() { if (MidfixList == null || MidfixList.Count <= 0) throw new Exception("中序列表為null或為空!"); } private void EnsurePostfixReady() { if (PostfixList == null) { PostfixList = DoParse(MidfixList); if (PostfixList == null || PostfixList.Count <= 0) throw new Exception("后序列表為null或為空!"); } } /// <summary> /// 判斷元素是否符合要求 /// </summary> /// <param name="ent"></param> /// <returns></returns> public bool IsSatisfy(object ent) { GuardMidfixListExist(); EnsurePostfixReady(); CalcStack.Clear(); foreach (var item in PostfixList) { if (item is ExpElem) { CalcStack.Push(item); continue; } #region And 運算 if (item is AndElem) { var op1 = CalcStack.Pop() as ExpElem; var op2 = CalcStack.Pop() as ExpElem; //任意一個是false則直接壓入false if (op1.Result == false || op2.Result == false) { CalcStack.Push(new ExpElem() { Result = false }); continue; } if (!op1.Result.HasValue && !op2.Result.HasValue) { op1.Compare(ent); if (op1.Result.Value == false) { CalcStack.Push(new ExpElem() { Result = false }); continue; } op2.Compare(ent); CalcStack.Push(new ExpElem() { Result = op2.Result }); continue; } if (!op1.Result.HasValue && op2.Result == true) { op1.Compare(ent); CalcStack.Push(new ExpElem() { Result = op1.Result }); continue; } if (op1.Result == true && !op2.Result.HasValue) { op2.Compare(ent); CalcStack.Push(new ExpElem() { Result = op2.Result }); continue; } if (op1.Result == true && op2.Result == true) { CalcStack.Push(new ExpElem() { Result = true }); continue; } } #endregion #region Or 運算 if (item is OrElem) { var op1 = CalcStack.Pop() as ExpElem; var op2 = CalcStack.Pop() as ExpElem; //任意一個是true則直接壓入true if (op1.Result == true || op1.Result == true) { CalcStack.Push(new ExpElem() { Result = true }); continue; } if (!op1.Result.HasValue && !op2.Result.HasValue) { op1.Compare(ent); if (!op1.Result == true) { CalcStack.Push(new ExpElem() { Result = true }); continue; } op2.Compare(ent); CalcStack.Push(new ExpElem() { Result = op2.Result }); } if (!op1.Result.HasValue && op2.Result == false) { op1.Compare(ent); CalcStack.Push(new ExpElem() { Result = op1.Result }); continue; } if (op1.Result == false && !op2.Result.HasValue) { op2.Compare(ent); CalcStack.Push(new ExpElem() { Result = op2.Result }); continue; } if (op1.Result == false && op2.Result == false) { CalcStack.Push(new ExpElem() { Result = false }); } } #endregion } return (CalcStack.Pop() as ExpElem).Result.Value; } /// <summary> /// 生成判斷函數 /// </summary> /// <returns></returns> public Expression<Func<T,bool>> GenIsSatisfyFunc<T>() { GuardMidfixListExist(); EnsurePostfixReady(); Stack<object> stack = new Stack<object>(); ParameterExpression entExp = Expression.Parameter(typeof(T), "ent"); foreach (var elem in PostfixList) { if (elem is ExpElem) { stack.Push(elem); continue; } if (elem is AndElem) { var elem1 = stack.Pop(); var elem2 = stack.Pop(); var exp= Expression.AndAlso(GetCallExpression(elem1,entExp),GetCallExpression(elem2,entExp)); stack.Push(exp); continue; } if(elem is OrElem) { var elem1 = stack.Pop(); var elem2 = stack.Pop(); var exp= Expression.OrElse(GetCallExpression(elem1,entExp),GetCallExpression(elem2,entExp)); stack.Push(exp); continue; } } LambdaExpression lambda= Expression.Lambda<Func<T,bool>>( stack.Pop() as Expression,entExp); return lambda as Expression<Func<T,bool>>; } private Expression GetCallExpression(object elem, ParameterExpression entExp) { if (elem is ExpElem) { return Expression.Call(Expression.Constant(elem), typeof(ExpElem).GetMethod("Compare"), entExp); } return elem as Expression; } /// <summary> /// 中序表達式轉后綴表達式 /// </summary> /// <param name="midfix"></param> /// <returns></returns> private List<Elem> DoParse(List<Elem> midfix) { Stack<Elem> stack = new Stack<Elem>(); var list=new List<Elem>(); foreach (var elem in midfix) { if (elem is ExpElem) { list.Add(elem); continue; } if (elem is LBElem) { stack.Push(elem); continue; } if (elem is RBElem) { var e = stack.Pop(); while (!(e is LBElem)) { list.Add(e); e = stack.Pop(); } continue; } if((elem is AndElem) || (elem is OrElem)) { if (stack.Count > 0) { var e = stack.Peek(); while ( !(e is LBElem) && elem.Priority <= e.Priority) { list.Add(stack.Pop()); if (stack.Count <= 0) break; e = stack.Peek(); } } stack.Push(elem); } } while (stack.Count > 0) { list.Add(stack.Pop()); } return list; } } #region 節點定義 public class Elem { public virtual string Name { get; set; } public virtual int Priority { get; set; } public Object Data { get; set; } } /// <summary> /// 左括號 /// 注意stack中只會壓入'(','And','Or' /// </summary> public class LBElem : Elem { public override string Name { get { return "("; } } public override int Priority { get { return 59; } } } /// <summary> /// 右括號 /// </summary> public class RBElem : Elem { public override string Name { get { return ")"; } } public override int Priority { get { return 99; } } } public class AndElem : Elem { public override string Name { get { return "And"; } } public override int Priority { get { return 88; } } } public class OrElem : Elem { public override string Name { get { return "Or"; } } public override int Priority { get { return 77; } } } public class ExpElem : Elem { public override int Priority { get { return 66; } } public bool Compare(object ent) { Console.WriteLine("計算了:" + Name); bool? ret=null; if (AssertType == Core.CompareType.Equal) { ret= string.Compare(GetV(ent),Value,true)==0; } if (AssertType == Core.CompareType.NotEqual) { ret = string.Compare(GetV(ent), Value, true) != 0; } if (AssertType == Core.CompareType.Greate) { ret = string.Compare(GetV(ent), Value, true) > 0; } if (AssertType == Core.CompareType.GreateOrEqual) { ret = string.Compare(GetV(ent), Value, true) >= 0; } if (AssertType == Core.CompareType.Less) { ret = string.Compare(GetV(ent), Value, true) < 0; } if (AssertType == Core.CompareType.LessOrEqual) { ret = string.Compare(GetV(ent), Value, true) <= 0; } if (AssertType == Core.CompareType.Contains) { ret = GetV(ent).Contains(Value); } if (AssertType == Core.CompareType.NoContains) { ret =! GetV(ent).Contains(Value); } if (AssertType == Core.CompareType.StartWith) { ret = GetV(ent).StartsWith(Value); } if (AssertType == Core.CompareType.EndWith) { ret = GetV(ent).EndsWith(Value); } if (!ret.HasValue) throw new Exception("未知的CompareType!"); Result = ret; return ret.Value; } public bool? Result { get; set; } public PropertyInfo Property { get; set; } public CompareType AssertType { get; set; } public string Value { get; set; } private string GetV(object ent) { var tmp= Property.GetValue(ent, null); if (tmp == null) tmp = string.Empty; return tmp.ToString(); } } public enum CompareType { Equal, NotEqual, Less, LessOrEqual, Greate, GreateOrEqual, Contains, NoContains, StartWith, EndWith }; #endregion }
參考:
逆波蘭式構建方法
1、從左至右掃描一中綴表達式。
2、若讀取的是操作數,則判斷該操作數的類型,並將該操作數存入操作數堆棧
3、若讀取的是運算符
(1) 該運算符為左括號"(",則直接存入運算符堆棧。
(2) 該運算符為右括號")",則輸出運算符堆棧中的運算符到操作數堆棧,直到遇到左括號為止,此時拋棄該左括號。
(3) 該運算符為非括號運算符:
(a) 若運算符堆棧棧頂的運算符為左括號,則直接存入運算符堆棧。
(b) 若比運算符堆棧棧頂的運算符優先級高,則直接存入運算符堆棧。
(c) 若比運算符堆棧棧頂的運算符優先級低或相等,則輸出棧頂運算符到操作數堆棧,
直至運算符棧棧頂運算符低於(不包括等於)該運算符優先級,或為左括號,並將當前運算符壓入運算符堆棧。
4、當表達式讀取完成后運算符堆棧中尚有運算符時,則依序取出運算符到操作數堆棧,直到運算符堆棧為空。
逆波蘭表達式求值算法:
1、循環掃描語法單元的項目。
2、如果掃描的項目是操作數,則將其壓入操作數堆棧,並掃描下一個項目。
3、如果掃描的項目是一個二元運算符,則對棧的頂上兩個操作數執行該運算。
4、如果掃描的項目是一個一元運算符,則對棧的最頂上操作數執行該運算。
5、將運算結果重新壓入堆棧。
6、重復步驟2-5,堆棧中即為結果值。
資源