2009-11-15 36 views
7

我用來創建接口,使用IEnumerable<T>作爲返回類型,只要我想指定某個輸出是隻讀的。我喜歡它,因爲它很簡約,隱藏了實現細節並將來自調用者的被調用者分開。IEnumerable <T>的異常處理存在問題,它依賴於惰性

但是最近我的一位同事爭辯說IEnumerable<T>應該只保留用於懶惰評估的情況,否則對於調用者方法來說不清楚,異常處理應該把它放在位置 - 圍繞方法調用或圍繞迭代。然後,對於只讀輸出的急切評估案例,我應該使用ReadOnlyCollection

聽起來對我來說很合理,但你會推薦什麼?你會同意IEnumerable的那個約定嗎?或者有更好的IEnumerable的異常處理方法嗎?

爲防萬一我的問題不清楚,我做了一個樣本類來說明問題。這裏有兩種方法具有完全相同的簽名,但它們需要不同的異常處理:

public class EvilEnumerable 
{ 
    IEnumerable<int> Throw() 
    { 
     throw new ArgumentException(); 
    } 

    IEnumerable<int> LazyThrow() 
    { 
     foreach (var item in Throw()) 
     { 
      yield return item; 
     } 
    } 

    public void Run() 
    { 
     try 
     { 
      Throw(); 
     } 
     catch (ArgumentException) 
     { 
      Console.WriteLine("immediate throw"); 
     } 

     try 
     { 
      LazyThrow(); 
     } 
     catch (ArgumentException) 
     { 
      Console.WriteLine("No exception is thrown."); 
     } 

     try 
     { 
      foreach (var item in LazyThrow()) 
      { 
       //do smth 
      } 
     } 
     catch (ArgumentException) 
     { 
      Console.WriteLine("lazy throw"); 
     } 
    } 
} 

更新1問題是不僅限於ArgumentException的。這是關於製作友好的類接口的最佳實踐,它告訴你他們是否返回延遲評估結果,因爲這會影響異常處理方法。

+5

假設IEnumerable應該保持懶惰評估是無意義的,因爲它從來沒有與懶惰評估掛鉤。在惰性評估是C#中的一個常見範例之前,IEnumerable就已存在。 – Joren 2009-11-15 21:21:17

回答

9

這裏真正的問題是延遲執行。在參數檢查的情況下,可以通過添加第二個方法做到這一點:

IEnumerable<int> LazyThrow() { 
    // TODO: check args, throwing exception 
    return LazyThrowImpl(); 
} 
IEnumerable<int> LazyThrowImpl() { 
    // TODO: lazy code using yield 
} 

異常發生;即使對於非延期結果(例如,List<T>),您也可能會收到錯誤(也許如果另一個線程在迭代時調整列表)。上述方法可讓您儘可能提前完成以減少yield的意外副作用並推遲執行。

+1

嗯,這是一個有用的代碼片段,但我的問題並不僅限於檢查參數,任何異常都可能發生在懶惰評估本身(在LazyThrowImpl中)。所以我想知道的是,如果只爲懶惰的評估案例保留IEnumerable 是一個合理的慣例,這樣可以幫助調用者理解異常處理代碼的位置。 – 2009-11-15 22:33:53

+1

我的觀點是任何異常都可能發生在* regular *評估期間,所以這是一個有爭議的問題。好的*更多*通常發生在懶惰的評估中,但我仍然會說*一般*不要擔心。 – 2009-11-16 07:45:26

2

我不同意你的同事。該方法的文檔遵循標準,它可能會拋出異常。將返回類型聲明爲IEnumerable不會使其只讀。它是否只讀取決於正在返回的接口的實際實現。主叫方不應該依賴於它是可寫的,但是這是從集合非常不同的只是被讀取

+0

對不起,我明白你在說什麼,但我不明白它是如何回答這個問題的( – 2009-11-15 22:40:09

+2

@yacoder我的觀點是沒有IEnumerable 與惰性評估沒有任何關係,所有BCL泛型集合都實現了該接口和列表等類似的東西是可以懶惰評估的,而且聲明返回類型並不是說它是隻讀的,它只是說你唯一可以依靠的就是獲得一個枚舉器 – 2009-11-16 14:23:20

0
在埃裏克利珀帖子討論

類似的問題:

http://blogs.msdn.com/ericlippert/archive/2007/09/05/psychic-debugging-part-one.aspx

http://blogs.msdn.com/ericlippert/archive/2007/09/06/psychic-debugging-part-two.aspx

不過,我不會將其視爲一般針對IEnumerable的參數。說實話,大多數時候收集我枚舉拋出一個異常,我不知道如何處理它,基本上它去了一些全局錯誤處理程序。當然,我同意,拋出異常的那一刻可能會讓人困惑。

+0

我的問題正是關於異常如果在我調用它的時候拋出它的異常,或者當我迭代它的結果時,僅僅從界面就不可能理解這個方法 – 2009-11-15 22:48:52

+0

也就是說,當你使用IEnumerable 來處理所有事情時 – 2009-11-15 22:49:23

3

從理論上講,我同意你的同事。如果有一些「工作」來創造結果,而且這項工作可能會導致一個例外,並且你不需要懶惰的評估,那麼確實讓結果變得懶惰只會使事情複雜化。

但是在實踐中,你無法查看返回類型並推斷出任何有用的東西。即使你創建了返回類型ReadonlyCollection,它仍然可能延遲評估和拋出,例如(ReadonlyCollection不是封閉的,所以一個子類可以顯式地實現IEnumerable接口並且做一些古怪的事情)。

在一天結束時,.Net類型系統不會幫助您理解大多數對象的異常行爲。

實際上,我仍然會選擇返回ReadonlyCollection而不是IEnumerable,但是因爲它與IEnumerable相比暴露了更多有用的方法(比如O(1)訪問第n個元素)。如果您打算創建一個由數組支持的清單集合,那麼您可能還需要向消費者返回有用的方面。 (您可能還會返回一個調用者擁有的'新鮮'陣列。)

+0

那麼當然,即使使用ReadOnlyCollection,也可以做一些令人討厭的事情,但它是可以的ld需要一些努力。雖然拋出一個懶惰的評估IEnumerable與渴望評估是毫不費力。返回myInternalCollection.Select(...)vs返回myInternalCollection.Select()。ToList() – 2009-11-15 22:46:20