談表達式樹的緩存(1):引言
2009-03-16 09:29 Jeffrey Zhao 閱讀(17304) 評論(24) 收藏 舉報表達式樹(Expression Tree)是.NET 3.5中引入的一種表達方式。表達式樹的運用十分廣泛,可以直觀地表現出各種“數據”,甚至“邏輯”和“行為”。再者,表達式樹是強類型的,因此合理地使用這個新特性可以讓代碼編寫變得優雅,方便。一個最簡單而常見的例子便是,某些朋友目前就已經喜歡使用表達式樹來代替傳統的ByXxx方法,尤其是在訪問一些直接支持表達式樹的數據源時(例如IEnumerable或LINQ to SQL)。如下:
public User GetUser(Expression<Func<User, bool>> predicate) { }
而不必寫成:
public User GetUserByID(int id) { } public User GetUserByName(string name) { }
于是在調用時便可以:
var user1 = GetUser(u => u.UserID == 1); var user2 = GetUser(u => u.Name == "jeffz");
姑且不論這種設計方式是否合適(因為即使這個做法不合理,也不能代表所有的用法),我們先達成一個共識,那就是“表達式樹很有用”——于是我們的接下去話題看上去才會比較有價值:P。那么就上面一個問題來說,在使用了表達式樹的情況下,如何在方法中進行緩存?在ByID或ByName的情況下,我們可以輕易地構造一些字符串作為緩存的鍵,例如“GetUserByID_100”或“GetUserByName_jeffz”。但是現在呢?每次在調用時就會生成一個不同的Expression對象,就算大家“表現一致”,也無法被“識別為”同樣的對象,而直接用作緩存的鍵,因此處理起來并不是那么直接了當的。
您可能會說,那么就在解析表達式樹的時候,識別出它是ByID還是ByName,然后再拼接出之前的字符串。當然,您如果真的是要解決上面這個例子的問題,那么的確可以用這種方法。但是老趙現在希望可以找到一種較為通用的,能夠根據表達式樹進行緩存的解決方案——事實上老趙本來就是在設計一個通用的功能時才引發了這個需求,而這個功能也打算在詳細談完緩存問題后與大家共享。
這個緩存問題看上去簡單,但是實際上在性能和功能進行權衡之后會有多種策略可以選擇。老趙會在這里談論5種緩存策略,它們各有千秋,有的方式資源很省,性能很好;而有的方式從性能上比較落后,資源占用也相對較高,但是在某些場景下它似乎還是唯一的解決方案。因此,至少我覺得討論一下這個問題也是非常有意思的事情,而且從一定程度上說,這些思考能夠在一定程度上體現出算法設計與數據結構的美妙之處(盡管相對來說它們其實非常簡單)。
在這一系列文章中,老趙希望可以重現自己在思考這個問題的時候所形成的完整思考路徑。相比最終解決方案,這可能才是更有價值的東西。文章有時也會將朋友們“引入歧途”,其目的也是為了讓弟兄們一起經歷一下老趙走過的彎路。到了最后,您可以會說“這死胖子真笨,怎么早沒想到”(呵呵,大家莫怪)。此外,這5種緩存策略也并非是思考的全部,事實上老趙相信還會有更好的解決方案(至少理論上是這樣的),而由于種種原因并沒有在這里實現出來。因此,老趙也希望大家在看了文章之后可以一起思考,并談出您的看法。:)
不過,表達式樹的“構造”很簡單,我們可以使用Lambda表達式輕松地生成一顆表達式樹,但是在具體操作時就較為困難了。因此在理解這一系列文章之前,可能您還需要作一些準備,也就是一些基礎的,操作表達式樹的方式。在操作表達式樹時,必不可少的東西便是一個ExpressionVisitor類,您可以在MSDN中找到其實現及相關示例,幾乎任何操作表達式樹的類都會繼承于它。總體來說,ExpressionVisitor類提供了功能可以概括為:
- Visit方法接受一個Expression類型的參數,并根據Expression的具體類型委托給特定的VisitXxx方法進行訪問,并將結果返回。
- 各VisitXxx方法會將得到的具體Expression類型的參數(如UnaryExpression)的各部分,即各個“子Expression”交給Visit方法進行訪問,并得到其返回值。
- 各VisitXxx方法如果發現每個Visit方法的調用都返回了原來的“子Expression”,那么則直接返回自身得到的參數,否則就構造一個新的Expression對象并返回。
以上是ExpressionVisitor類的功能描述,希望朋友們可以自行閱讀一下它的代碼。最好還可以自行實踐一番——至少可以閱讀一下MSDN中的示例。
最后,我們將實現幾個類,它們都實現同一個接口IExpressionCache,如下:
public interface IExpressionCache<T> where T : class { T Get(Expression key, Func<Expression, T> creator); }
接口中只有一個Get方法,如果沒有對應當前key的value,那么則會通過creator委托創建一個新的value并返回。
完整代碼下載:http://code.msdn.microsoft.com/ExpressionCache
相關文章:
浙公網安備 33010602011771號