[.net 物件導向程式設計進階] (7) Lamda表示式(三) 表示式樹高階應用

yubinfeng發表於2015-07-06

[.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>

.NET 技術交流群:467189533    .NET 程式設計

==============================================================================================   

相關文章