2015-11-02 99 views
0

我有下面的代碼,其對任何類型的對象的一個​​空值或空白檢查:C# - 最快的方法來做這種類型的空檢查?

public static void IfNullOrEmpty(Expression<Func<string>> parameter) 
    { 
    Throw.IfNull(parameter); 
    if (parameter.GetValue().ToString().Length == 0) 
    { 
     throw new ArgumentException("Cannot be empty", parameter.GetName()); 
    } 
    } 

它調用下面的GetValue擴展方法:

public static T GetValue<T>(this Expression<Func<T>> parameter) 
    { 
    MemberExpression member; 
    Expression expression; 

    member = (MemberExpression)parameter.Body; 
    expression = member.Expression; 

    return (T)parameter.Compile()(); 
    } 

我傳遞在含有表達字符串在這個方法中進行測試。這種方法在我的機器上平均需要2 ms(即使在我測試的另一臺機器上速度更慢),如果在整個應用程序中多次調用該方法,該方法會相加。看起來這種方法太慢了。什麼是做這種空檢查的最快方法?

+0

你可能會計時JIT。對於新手來說,他們應該有一個很大的警告,不要試圖去計時,他們總是會犯錯。 – Blindy

+0

當我整個分析應用程序時,我看到類似的結果,就好像我只是單獨計時代碼一樣。 – Andrew

+1

爲什麼你使用表達呢?爲什麼不只是'功能'? – Enigmativity

回答

1

編譯表達式自然需要很多工作。如果這段代碼經常運行,我通常會做的事情是我只編譯一次表達式並保存編譯後的代表以備後用。

有可能保持「正常」緩存但緩存是有效的,你需要一個好的哈希函數,我不看你怎麼能有這樣的在這裏。您需要稍微重構一下代碼,以便每個使用GetValue的地方都可以正確訪問已編譯的委託。沒有看到更多的代碼,我不能給你任何暗示。

可能有很多原因讓您看到以下呼叫速度更快。由於難以哈希,我不期望那一個。更有可能你會看到一個現代CPU的作品,它會做很多猜測來快速運行代碼。如果你只運行了相同的表達式,那麼CPU可以猜測下一次調用的更多信息,並且運行得更快。總是有GC也要考慮。測試猜測的想法

的一種方法是創建一個具有幾個不同的表情大陣。做一個測試,它是按表達式排序的,以及是隨機的。如果我的懷疑成立,第一個應該會更快。

+0

如何保存已編譯的代表?我可以重複使用相同的委託來傳遞任何類型的字符串或對象嗎? – Andrew

+0

也許把你的方程式放在一個班級裏。當存儲一個值時,緩存被標記爲無效。當您嘗試閱讀結果並且無效時,請重新計算結果。 –

+0

@Andrew如果你可以在你使用這種方法的地方分享一些代碼,我可能會提出一個解決方案。有幾種方法,但沒有一個足夠小以適應每種情況。 –

0

如果我正確地閱讀你的代碼,你需要一個Expression的唯一原因是,如果檢查失敗,你將能夠提取參數的名字並將它傳遞給你的異常扔,對吧?如果是這樣,這是一個非常陡峭的價格來支付一個稍微更方便的錯誤消息,(希望)只發生在極少數情況下。

2ms的開銷比我期望的略高,但巨大的開銷是難以或不可能避免在這裏。你本質上是迫使運行時遍歷一個表達式樹,將它翻譯成MSIL,然後通過JIT再次將該MSIL轉換並優化爲可執行代碼,只需執行!= null檢查,除非開發人員在某處犯了錯誤。

你可能會想出某種緩存機制;實體框架通過遍歷表達式樹並構建它的散列來緩存表達式,並將其用作字典的關鍵字,其中編譯後的表達式作爲委託來存儲。在每次調用時,它都比通過JIT發送便宜得多,但與簡單的!= null檢查相比,它的價格仍然要高出數十億美元(特別是在考慮現代分支預測CPU時) 。

所以在我看來,這種方法是一個不錯的主意,但根本不值得,當你考慮成本和替代品是非常痛苦的時候(尤其是新的nameof運營商)。這也是相當脆弱,因爲如果我是一個開發誰認爲他能做到這一點:

Throw.IfNullOrEmpty(() => clientId + "something")

你投以MemberExpression會失敗在那裏。

同樣,我們有理由有人認爲因爲在表達式中傳遞和表達僅僅是因爲數據的代碼,這將是安全的,做到這一點:

Throw.IfNullOrEmpty(() => parent.Child.MightBeNull.MightAlsoBeNull.ClientID)

這是如果部分遍歷表達式樹,則完全可能安全地評估該表達式,但在您的示例中,整個表達式將立即編譯並執行,並且可能會在此處失敗,並顯示NullReferenceException

我想這歸結於Expression<Func<T>>類型的參數對於使用空檢查來說不夠嚴格。你可以做各種奇怪的東西,編譯器會非常高興,但是你會在運行時得到意想不到的結果。

相關問題