2013-07-17 24 views
2

我只是花了一些時間撓了腦袋,當一個斷點似乎神奇地出現兩次在同一個地方,在一個枚舉。防止意外重新枚舉IEnumerable的技巧?

原來的錯誤是一個簡單的監督:

protected override void Extract() 
    { 
     LogGettingOffers(); 
     var offerIds = CakeMarketingUtility.OfferIds(advertiserId); 
     LogExtractingClicks(offerIds); 
     foreach (var offerId in offerIds) 
     { 
      int rowCount; 
      var clicks = RetryUtility.Retry(3, 10000, new[] { typeof(Exception) },() => 
      { 
       return CakeMarketingUtility.EnumerateClicks(dateRange, advertiserId, offerId); 
      }); 
      foreach (var clickBatch in clicks.InBatches(1000)) 
      { 
       LogExtractedClicks(offerId, clickBatch); 

       // SHOULD BE clickBatch, NOT clicks 
       Add(clicks); 
      } 
     } 
     End(); 
    } 

這使我想知道什麼(如果有的話),其中的一種可能需要編寫代碼捕獲這樣的錯誤的預防措施。

注意,我還不能肯定是有意義的走這條思路 - 也許答案是「不寫不正確的代碼」,這我很願意接受..

這裏的這產生的結果實際代碼:

public static IEnumerable<Click> EnumerateClicks(DateRange dateRange, int advertiserId, int offerId) 
    { 
     // initialize to start at the first row 
     int startAtRow = 1; 

     // hard code an upper limit for the max number of rows to be returned in one call 
     int rowLimitForOneCall = 5000; 

     bool done = false; 
     int total = 0; 
     while (!done) 
     { 
      Logger.Info("Extracted a total of {0} rows, checking for more, starting at row {1}..", total, startAtRow); 

      // prepare the request 
      var request = new ClicksRequest 
      { 
       start_date = dateRange.FromDate.ToString("MM/dd/yyyy"), 
       end_date = dateRange.ToDate.ToString("MM/dd/yyyy"), 
       advertiser_id = advertiserId, 
       offer_id = offerId, 
       row_limit = rowLimitForOneCall, 
       start_at_row = startAtRow 
      }; 

      // create the client, call the service and check the response 
      var client = new ClicksClient(); 
      var response = client.Clicks(request); 
      if (!response.Success) 
      { 
       throw new Exception("ClicksClient failed"); 
      } 

      // update the running total 
      total += response.RowCount; 

      // return result 
      foreach (var click in response.Clicks) 
       yield return click; 

      // update stopping condition for loop 
      done = (response.RowCount < rowLimitForOneCall); 

      // increment start row for next iteration 
      startAtRow += rowLimitForOneCall; 
     } 

     Logger.Info("Extracted a total of {0}, done.", total); 
    } 
+0

說實話,我相信你的直覺是正確的:它只是不值得這個得太多。任何可能的選擇可能會更復雜。編寫更復雜的代碼以防止愚蠢的編碼錯誤感覺就像我的代碼味道。 –

回答

1

對於這個特定的問題,我會說,解決的辦法是「不寫不正確的代碼」。尤其是當結果可以在不改變任何狀態的情況下生成的時候(比如當你枚舉列表中的元素時),我認爲應該可以從任何枚舉中創建多個枚舉器。

您可以創建一個IEnumerable包裝,確保GetEnumerator只被調用一次,但如果您確實需要合理調用它兩次,該怎麼辦?你真正想要的是捕捉錯誤,而不是捕捉被枚舉的枚舉多次,這不是你可以輕鬆放入軟件解決方案的東西。

也許問題是clickBatchclicks有相同的類型,所以編譯器可能也不區分。

+0

它可能是很好,如果我可以應用方法屬性的東西像[NotReentrantOnSameThread] ...無論如何,感謝您的建議,我會很高興我的錯誤已經消失 –

1

有些時候我需要確保我公開的枚舉僅被調用一次。例如:返回流式信息,我只有一個讀取可用,或者非常昂貴的查詢。

試試下面的擴展類:

public static class Extensions 
{ 
    public static IEnumerable<T> SingleEnumeration<T>(this IEnumerable<T> source) 
    { 
     return new SingleEnumerator<T>(source); 
    } 
} 

public class SingleEnumerator<T> : IEnumerable<T> 
{ 
    public SingleEnumerator(IEnumerable<T> source) 
    { 
     this.source = source; 
    } 

    public IEnumerator<T> GetEnumerator() 
    { 
     // return an empty stream if called twice (or throw) 
     if (source == null) 
      return (new T[0]).AsEnumerable().GetEnumerator(); 

     // return the actual stream 
     var result =source.GetEnumerator(); 
     source = null; 
     return result; 
    } 

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 
    { 
     // return an empty stream if called twice (or throw) 
     if (source == null) 
      return (new T[0]).AsEnumerable().GetEnumerator(); 

     var result = source.GetEnumerator(); 
     source = null; 
     return result; 
    } 

    private IEnumerable<T> source; 
} 
+0

這應該是答案。 「不要寫錯誤的代碼」坦白地說不是一個好的解決方案,因爲我們都知道這是一個大型項目。我也會得到Resharper,這對這種事情有很大的幫助。 – Telavian