2009-12-16 46 views
5

假設我要檢查組對象,以確保沒有爲空:我可以在方法調用中強制自己的短路嗎?

if (obj != null && 
    obj.Parameters != null && 
    obj.Parameters.UserSettings != null) { 

    // do something with obj.Parameters.UserSettings 
} 

這是一個誘人的前景寫一個輔助函數來接受可變數量的參數,並簡化這種檢查:

static bool NoNulls(params object[] objects) { 
    for (int i = 0; i < objects.Length; i++) 
     if (objects[i] == null) return false; 

    return true; 
} 

接着上面的代碼將變成:

if (NoNulls(obj, obj.Parameters, obj.Parameters.UserSettings)) { 
    // do something 
} 

,對嗎? 錯誤。如果obj爲空,那麼當我嘗試將obj.Parameters傳遞給NoNulls時,我會得到NullReferenceException

所以上述方法顯然是錯誤的。但if聲明使用&&運算符工作得很好,因爲它是短路的。所以:是否有任何方法使方法短路,以便它的參數不被評估,直到在方法中明確引用?

+11

你真正需要解決這個問題並不是懶惰的方法論證評估 - 儘管如你所見,這可以做到這一點。最好是運算符x.?y具有(x == null?null:x.y)的語義 - 這樣你可以說「if(obj.?Parameters.?UserSettings == null)」。我們考慮過C#4的這樣一個運算符,但從未實現它。也許對於假設的未來版本。 – 2009-12-16 20:59:00

+0

這真的很有趣。有沒有其他語言實現等效的運算符? – 2009-12-16 21:08:14

+1

@Chris:Groovy呢。它被稱爲無效的解除引用操作符。很高興聽到C#團隊考慮到它並可能再次考慮它:) – 2009-12-16 21:21:35

回答

9

好了,這是醜陋的,但...

static bool NoNulls(params Func<object>[] funcs) { 
    for (int i = 0; i < funcs.Length; i++) 
     if (funcs[i]() == null) return false; 

    return true; 
} 

然後調用它:

if (NoNulls(() => obj, 
      () => obj.Parameters, 
      () => obj.Parameters.UserSettings)) { 
    // do something 
} 

基本上你提供委託延遲計算的值,而不是值本身(因爲評估這些值是導致異常的原因)。

我不是說這是不錯,但它的存在作爲一個選項...

編輯:其實,這(和意外)獲取到什麼丹後的心臟,我想。 全部在方法本身執行之前評估方法的參數。有效地使用委託讓您可以延遲評估,直到方法需要調用委託來檢索值。

+4

這是醜陋的,令人困惑的,但不是空合併運算符的竅門:'if((object z = a ?? b ?? c)!= null)DoSomething();' – LBushkin 2009-12-16 20:49:34

+0

另一個想法是通過將單個lambda表達式作爲表達式樹用於可以分解表達式樹並執行短路評估的方法。 – LBushkin 2009-12-16 20:51:02

+0

我其實是想過,不管你信不信。當然,對於給定的目的 - 檢查空值 - 這需要與原始方法大致相同的代碼量,因此並不是那麼有用。當然,爲了其他目的,它可能會更好。然而,我不想使用它的主要原因是它需要使用lambda表達式,這對於VB來說是不可用的(至少在VB 10之前,我想呢?),並且我想要一些有用的既C#和VB,因爲我們在工作中使用這兩種語言。 – 2009-12-16 20:52:50

1

您可以編寫一個接受表達式樹的函數,並將該樹轉換爲將檢查空值的表單,並返回Func<bool>,該表單可以安全地評估以確定是否存在空值。

我懷疑,雖然最終的代碼可能很酷,但它會令人困惑,並且性能遠遠低於僅編寫一堆短路檢查。事實上,它可能不如僅檢查所有值並捕獲NullReferenceException(不是我主張異常處理作爲控制機制流)的性能。

這種功能的簽名會是這樣的:

public static Func<bool> NoNulls(Expression<Func<object>> expr) 

,它的使用將是這個樣子:

NoNulls(() => new { a = obj, 
        b = obj.Parameters, 
        c = obj.Parameters.UserSettings })(); 

如果我得到一些自由時間,我會寫一個函數,只是這樣的表達樹轉換和更新我的帖子。不過,我相信Jon Skeet或Mark Gravell可以用一隻眼睛閉上一隻手,並用一隻手在背後寫下這樣的功能。

我也希望看到C#實現Eric所暗示的.?運算符。正如一個不同的埃裏克(卡特曼)可能會說,那會「踢屁股」。

+1

你不需要使它成爲三個單獨的參數 - 只需使用'()=> obj.Parameters.UserSettings'並獲取表達式樹來檢查結果每個屬性調用之間爲空。 – 2009-12-16 23:39:51

0

如果您不介意丟失靜態類型安全性,則可以使用反射。我不介意,所以我只是使用你的第一個短路結構。像埃裏克提到的功能將是受歡迎的:)

我已經想過幾次這個問題。 Lisp的宏可以用你提到的方式解決問題,因爲它們允許你自定義你的評估。

我也嘗試過使用擴展方法來解決這個問題,但沒有什麼比原始代碼更難看。

編輯:(回覆不要讓我插入代碼塊,所以編輯我的帖子)

哎呀,沒跟上這一點。對不起,關於:)

您可以使用反射查找和評估通過字符串成員或屬性。一類我的一個朋友寫了像語法:

new ReflectionHelper(obj)["Parameters"]["UserSettings"] 

它通過方法鏈合作,在每個級別返回ReflectionHelper。我知道NullReferenceException在這個例子中是一個問題。我只是想演示如何將評估推遲到運行時。

一個例子稍微更接近於有用的:

public class Something 
{ 
    public static object ResultOrDefault(object baseObject, params string[] chainedFields) 
    { 
    // ... 
    } 
} 

再次,此語法臭。但是,這表明使用字符串+反射來推遲評估運行時。

+0

出於好奇,如何用反射來達到這個目的呢?問題似乎是,一旦你輸入了方法,它的所有參數都已經被評估過了。我不清楚如何反思可以彌補這一點;但也許我錯過了一些東西。 – 2009-12-17 15:23:49

+0

修改帖子以回覆您的評論 – 2010-02-04 05:06:51

相關問題