天天看點

艾偉:這下沒理由嫌Eval的性能差了吧?

  寫ASP.NET中使用Eval是再常見不過的手段了,好像任何一本ASP.NET書裡都會描述如何把一個DataTable綁定到一個控件裡去,并且通過Eval來取值的用法。不過在目前的DDD(Domain Driven Design)時代,我們操作的所操作的經常是領域模型對象。我們可以把任何一個實作了IEnumerable的對象作為綁定控件的資料源,并且在綁定控件中通過Eval來擷取字段的值。如下:

  在這裡,Eval對象就會通過反射來擷取Title和Content屬性的值。于是經常就有人會見到說:“反射,性能多差啊,我可不用!”。在這裡我還是對這種追求細枝末節性能的做法持保留态度。當然,在上面的例子裡我們的确可以換種寫法:

  我們通過Container.DataItem來擷取目前周遊過程中的資料對象,将其轉換成Comment之後讀取它的Title和Content屬性。雖然表達式有些長,但似乎也是個不錯的解決方法。性能嘛……肯定是有所提高了。

  但是,在實際開發過程中,我們并不一定能夠如此輕松的将某個特定類型的資料作為資料源,往往需要組合兩種對象進行聯合顯示。例如,我們在顯示評論清單時往往還會要顯示發表使用者的個人資訊。由于C# 3.0中已經支援了匿名對象,是以我們可以這樣做:

  我們通過LINQ級聯Comment和User資料集,可以輕松地構造出構造出作為資料源的匿名對象集合(有沒有看出LINQ的美妙?)。上面的匿名對象将包含Title,Content和NickName幾個公有屬性,是以在頁面中仍舊使用Eval來擷取資料,不提。

  不過我幾乎可以肯定,又有人要叫了起來:“LINQ沒有用!我們不用LINQ!Eval性能差!我們不用Eval!”。好吧,那麼我免為其難地為他們用“最踏實”的技術重新實作一遍:

  兄弟們自己做判斷吧。

  反射速度慢?我同意它是相對慢一些。

  反射占CPU多?我同意他是相對多一點。

  是以Eval不該使用?我不同意——怎能把孩子和髒水一起倒了?我們把反射通路屬性的性能問題解決不就行了嗎?

  性能差的原因在于Eval使用了反射,解決這類問題的傳統方法是使用Emit。但是.NET 3.5中現在已經有了Lambda Expression,我們動态構造一個Lambda Expression之後可以通過它的Compile方法來獲得一個委托執行個體,至于Emit實作中的各種細節已經由.NET架構實作了——這一切還真沒有太大難度了。

  在DynamicPropertyAccessor中,我們為一個特定的屬性構造一個形為o => object((Class)o).Property的Lambda表達式,它可以被Compile為一個Func

測試一下性能?

  我們來比對一下屬性的直接擷取值,反射擷取值與……Lambda表達式擷取值三種方式之間的性能。

  結果如下:

  使用了DynamicPropertyAccessor之後,性能雖比直接調用略慢,也已經有百倍的差距了。更值得一提的是,DynamicPropertyAccessor還支援對于匿名對象的屬性的取值。這意味着,我們的Eval方法完全可以依托在DynamicPropertyAccessor之上。

  “一步之遙”?沒錯,那就是緩存。調用一個DynamicPropertyAccessor的GetValue方法很省時,可是構造一個DynamicPropertyAccessor對象卻非常耗時。是以我們需要對DynamicPropertyAccessor對象進行緩存,如下:

  經過測試之後發現,由于每次都要從緩存中擷取DynamicPropertyAccessor對象,調用性能有所下降,但是依舊比反射調用要快幾十上百倍。

  FastEval方法,如果在之前的.NET版本中,我們可以将其定義在每個頁面的共同基類裡。不過既然我們在用.NET 3.5,我們可以使用Extension Method這種沒有任何侵入的方式來實作:

  我們在Control上的擴充,確定了每個頁面中都可以直接通過一個對象和屬性名擷取一個值。而在TemplateControl上的擴充,則使得各類可以綁定控件或頁面(Page,MasterPage,UserControl)都可以直接通過屬性名來擷取目前正在綁定的那個資料對象裡的屬性值。

  現在,您還有什麼理由拒絕FastEval?

  其實我們整篇文章都小看了Eval方法的作用。Eval方法的字元串參數名為“expression”,也就是表達式。事實上我們甚至可以使用“.”來分割字元串以擷取一個對象深層次的屬性,例如<%# Eval("Content.Length") %>。那麼我們的FastEval可以做到這一點嗎?當然可以——隻不過這需要您自己來實作了。:)

  最後再留一個問題供大家思考:現在DynamicPropertyAccessor隻提供一個GetValue方法,那麼您能否為其添加一個SetValue方法來設定這個屬性呢?希望大家踴躍回複,稍後我将提供我的做法。

繼續閱讀