[.net 面向對象程序設計進階] (7) Lamda表達式(三) 表達式樹高級應用


[.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 程序設計

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM