[.net 物件導向程式設計進階] (7) Lamda表示式(三) 表示式樹高階應用
本節導讀:討論了表示式樹的定義和解析之後,我們知道了表示式樹就是並非可執行程式碼,而是將表示式物件化後的資料結構。是時候來引用他解決問題。而本節主要目的就是使用表示式樹解決實際問題。
讀前必備:
本節學習前,需要掌握以下知識:
A.繼承 [.net 物件導向程式設計基礎] (12) 物件導向三大特性——繼承
B.多型 [.net 物件導向程式設計基礎] (13) 物件導向三大特性——多型
C.抽象類 [.net 物件導向程式設計基礎] (15) 抽象類
D.泛型 [.net 物件導向程式設計基礎] (18) 泛型
1.動態建立表示式樹
上一節中通過對錶達式樹結構和解析表示式的學習以後,動態建立表示式樹,已經變得非常簡單了,下面我們使用表示式樹動態建立下節的Lambda表示式.
先看我們要最終完成的原表示式:
Expression<Func<int, int, bool>> expression = (x, y) => x!=0 && x==y+1;
動態建立過程如下:
//動態建立表示式樹 Expression<Func<int, int, bool>> expression = (x, y) => x != 0 && x == y + 1; //先建立兩個引數 ParameterExpression[] parameters = new ParameterExpression[] { Expression.Parameter(typeof(int),"x"), Expression.Parameter(typeof(int), "y") }; ParameterExpression param1 = parameters[0]; ParameterExpression param2 = parameters[1]; //下面先建立右邊第一個表示式 x!=0 //(1)常量 0x ConstantExpression rightLeftRight = Expression.Constant(0, typeof(int)); //(2)建立左邊第一個表示式 x!=0 BinaryExpression rightLeft = Expression.NotEqual(param1, rightLeftRight); //下面建立右邊第二個表示式 x==y+1 //(3) 先建立右邊部分表示式y+1 BinaryExpression rightRightRight = Expression.Add(param2, Expression.Constant(1, typeof(int))); //(4)建立右邊表示式 x==y+1 BinaryExpression rightRight = Expression.Equal(param1, rightRightRight); //5)建立表示式 右部整體 x != 0 && x == y + 1 BinaryExpression Right = Expression.AndAlso(rightLeft, rightRight); //(6)完成整個表示式 Expression<Func<int, int, bool>> lambdaExr = Expression.Lambda<Func<int, int, bool>>(Right,parameters); Console.Write(lambdaExr.ToString());
執行結果如下:
上面建立過程如下:
2.執行表示式樹
動態建立完成上面的表示式,我們肯定最終結果是要使用這個表示式進行處理一些問題,對於動建立的表示式樹如何執行呢?
這個問題非常容易,只需要兩個步聚:
A.Compiling 程式設計表示式樹為委託
B.呼叫表示式樹(呼叫該委託)
下面看示例:
//執行表示式樹 Expression<Func<int, int, bool>> expression = (x, y) => x != 0 && x == y + 1; Func<int, int, bool> result = expression.Compile(); bool result2= expression.Compile()(9,8); Console.WriteLine(result2); Console.WriteLine(result(3, 2)); Console.WriteLine(result(5, 4)); Console.WriteLine(result(6, 4)); Console.WriteLine(result(-6, -7));
執行結果如下:
3.除錯表示式樹
在除錯應用程式時,您可以分析表示式樹的結構和內容。 若要快速瞭解表示式樹結構,您可以使用 DebugView 屬性,該屬性僅在除錯模式下可用。 使用 Visual Studio 進行除錯。為了更好地表示表示式A.樹的內容,DebugView 屬性使用 Visual Studio 視覺化工具。
在“資料提示”、“監視”視窗、“自動”視窗或“區域性變數”視窗中,單擊表示式樹的 DebugView 屬性旁邊顯示的放大鏡圖示。將會顯示視覺化工具列表。
B.單擊要使用的視覺化工具。
比如我們使用文字視覺化工具
$符號,表示 引數
4.修改表示式樹
表示式樹是不可變的,這意味著不能直接修改表示式樹。
若要更改表示式樹,必須建立現有表示式樹的一個副本,並在建立副本的過程中執行所需更改。 您可以使用 ExpressionVisitor 類遍歷現有表示式樹,並複製它訪問的每個節點。
.NET 有一ExpressionVisitor 類提供重寫來修改表示式樹
下面我們看一下如何通過重寫VisitBinary方法將表示式樹左邊節點型別由 && 轉為 ||,實現如下:
public class OrElseModifier : ExpressionVisitor { public Expression Modify(Expression expression) { return Visit(expression); } protected override Expression VisitBinary(BinaryExpression b) { if (b.NodeType == ExpressionType.AndAlso) { Expression left = this.Visit(b.Left); Expression right = this.Visit(b.Right); return Expression.MakeBinary(ExpressionType.OrElse, left, right, b.IsLiftedToNull, b.Method); } return base.VisitBinary(b); } }
呼叫如下:
//修改表示式樹 Expression<Func<int, int, bool>> expression = (x, y) => x != 0 && x == y + 1; OrElseModifier amf = new OrElseModifier(); Expression newExp= amf.Modify(expression); Console.WriteLine("原表示式: "+ expression.ToString()); Console.WriteLine("修改後的表示式:"+newExp.ToString());
執行結果如下:
對於上面的實現,有幾點要說明,上面.NET提供給我們的類ExpressionVisitor 有很多可重寫的方法供我們完成對錶達式的間接修改,返回一個表示式副本,也就是新的表示式。
我們在呼叫階段為什麼要使用Modify(expression);來呼叫,這點,.net在設計的時候,使用了一種設計模式,就是訪問者模式。
我們可以看到VisitBinary是一個保護的成員,當然我們在重寫的時候是不能修改原方法的修飾符的。
這一點小夥伴們在[.net 物件導向程式設計基礎] (13) 物件導向三大特性——多型一節中可以詳細瞭解。
對於設計模式,我如果有時間,會寫這方面的東西,部落格園相關的文章也是非常多。
5.使用表示式樹來生成動態查詢
我們做一個有意思的示例,分類查詢我在部落格園中的文章。
第一步,我們先獲取文章列表,通過一個實體列表來存放資料
先建立實體:
/// <summary /// 我的部落格文章實體類 /// </summary> public class MyArticle { /// <summary> /// 文章編號 /// </summary> public int id { get; set; } /// <summary> /// 文章標題 /// </summary> public string title { get; set; } /// <summary> /// 文章摘要 /// </summary> public string summary { get; set; } /// <summary> /// 釋出時間 /// </summary> public DateTime published { get; set; } /// <summary> /// 最後更新時間 /// </summary> public DateTime updated { get; set; } /// <summary> /// URL地址 /// </summary> public string link { get; set; } /// <summary> /// 推薦數 /// </summary> public int diggs { get; set; } /// <summary> /// 瀏覽量 /// </summary> public int views { get; set; } /// <summary> /// 評論數 /// </summary> public int comments { get; set; } /// <summary> /// 作者 /// </summary> public string author { get; set; } }
接下來獲取文章
//動態查詢 我在部落格園中的文章分類查詢 //第一步,獲取我在部落格園中的文章 List<MyArticle> myArticleList = new List<MyArticle>(); var document = XDocument.Load( "http://wcf.open.cnblogs.com/blog/u/yubinfeng/posts/1/100" ); var elements = document.Root.Elements(); //在進行這個工作之前,我們先獲取我部落格中的文章列表 var result = elements.Where(m => m.Name.LocalName == "entry").Select(myArticle => new MyArticle { id = Convert.ToInt32(myArticle.Elements().SingleOrDefault(x => x.Name.LocalName == "id").Value), title = myArticle.Elements().SingleOrDefault(x => x.Name.LocalName == "title").Value, published = Convert.ToDateTime(myArticle.Elements().SingleOrDefault(x => x.Name.LocalName == "published").Value), updated = Convert.ToDateTime(myArticle.Elements().SingleOrDefault(x => x.Name.LocalName == "updated").Value), diggs = Convert.ToInt32(myArticle.Elements().SingleOrDefault(x => x.Name.LocalName == "diggs").Value), views = Convert.ToInt32(myArticle.Elements().SingleOrDefault(x => x.Name.LocalName == "views").Value), comments = Convert.ToInt32(myArticle.Elements().SingleOrDefault(x => x.Name.LocalName == "comments").Value), summary = myArticle.Elements().SingleOrDefault(x => x.Name.LocalName == "summary").Value, link = myArticle.Elements().SingleOrDefault(x => x.Name.LocalName == "link").Attribute("href").Value, author = myArticle.Elements().SingleOrDefault(x => x.Name.LocalName == "author").Elements().SingleOrDefault(x => x.Name.LocalName == "name").Value }); myArticleList.AddRange(result);
建立一個查詢表示式樹的方法
public static IQueryable<T> MySearchList(IQueryable<T> myArticleTable, T myArticle) { //第二步,動態查詢我的文章 // List<MyArticle> SearchMyArticleList = new List<MyArticle>(); //1.我們先定義幾個查詢的引數(文章標題,瀏覽數,釋出時間) ParameterExpression myart = Expression.Parameter(typeof(T), "article"); //標題 ParameterExpression searchTitle = Expression.Parameter(typeof(string), "searchTitle"); //標題 ParameterExpression searchViews = Expression.Parameter(typeof(int), "searchViews"); //瀏覽數 ParameterExpression searchPublished = Expression.Parameter(typeof(DateTime), "searchPublished");//建立月份 //2.使用表示式樹,動態生成查詢 (查詢某個日期的文章) Expression left = Expression.Property(myart, typeof(T).GetProperty("published")); //訪問屬性的表示式 Expression right = Expression.Property(Expression.Constant(myArticle), typeof(T).GetProperty("published"));//訪問屬性的表示式 Expression e1 = Expression.GreaterThanOrEqual(left, right); //大於等於 //2.使用表示式樹,動態生成查詢 (按點選數查詢) Expression left2 = Expression.Property(myart, typeof(T).GetProperty("views")); //訪問屬性的表示式 Expression right2 = Expression.Property(Expression.Constant(myArticle), typeof(T).GetProperty("views"));//訪問屬性的表示式 Expression e2 = Expression.GreaterThanOrEqual(left2, right2); //3.構造動態查詢 (按點選數和月份查詢) Expression predicateBody = Expression.AndAlso(e1, e2); //4.構造過濾 MethodCallExpression whereCallExpression = Expression.Call( typeof(Queryable), "Where", new Type[] { typeof(T) }, myArticleTable.Expression, Expression.Lambda<Func<T, bool>>(predicateBody, new ParameterExpression[] { myart })); //構造排序 MethodCallExpression orderByCallExpression = Expression.Call( typeof(Queryable), "OrderByDescending", new Type[] { typeof(T), typeof(int) }, whereCallExpression, Expression.Lambda<Func<T, int>>(left2, new ParameterExpression[] { myart })); //建立查詢表示式樹 IQueryable<T> results = myArticleTable.Provider.CreateQuery<T>(orderByCallExpression); return results; }
呼叫方法
IQueryable<MyArticle> results = ExpressionTree<MyArticle>.MySearchList(myArticleList.AsQueryable<MyArticle>(), new MyArticle() { views=500, published=Convert.ToDateTime("2015-06")}); foreach (MyArticle article in results) Console.WriteLine(article.title + " \n [釋出日期:"+article.published+"] [瀏覽數:"+article.views+"]");
執行結果如下:
我們查詢的是 釋出日期在6月1日以後,點選量在500以上的文章
6.要點:
本節通過動態建立表示式樹、執行表示式樹及表示式樹的除錯的學習,最後通過一個動態查詢部落格園文章結束,使小夥伴們能熟練認識表示式樹在動態查詢上帶來的便利。
[花絮]:晚上寫部落格過程中,我家汪一直抓了我N次,讓我時刻保持清醒狀態,最終完成本篇,下面是家汪的靚照:
==============================================================================================
<如果對你有幫助,記得點一下推薦哦,如有有不明白或錯誤之處,請多交流>
<對本系列文章閱讀有困難的朋友,請先看《.net 物件導向程式設計基礎》>
<轉載宣告:技術需要共享精神,歡迎轉載本部落格中的文章,但請註明版權及URL>
==============================================================================================