2013-05-11 64 views
1

我有這個問題,我一直在試圖弄清楚。 我試圖使CustomStack行爲像堆棧,只實現Push(T),Pop(),Peek()和Clear()方法。我有這個代碼,我認爲這是正確的,但輸出只顯示一半的數字。我認爲這與推送方法有關,但我看不出它有什麼問題。自定義堆棧<T> IEnumerable <T>和數組?

using System; 
using System.Collections.Generic; 
using System.Collections; 
using System.Linq; 
using System.Text; 

namespace Enumerator 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      CustomStack<int> collection = new CustomStack<int>(); 

      for (int i = 0; i < 30; i++) 
      { 
       collection.Push(i); 
       Console.WriteLine(collection.Peek()); 
      } 
      collection.Push(23); 
      foreach (int x in collection) 
      { 
       Console.WriteLine(collection.Pop()); 
      } 

      Console.WriteLine("current", collection.Peek()); 
      Console.ReadKey(); 
     } 
    } 

    public class CustomStack<T> : IEnumerable<T> 
    { 

     private T[] arr; 
     private int count; 

     public CustomStack() 
     { 
      count = 0; 
      arr = new T[5]; 
     } 


     public T Pop() 
     { 
      int popIndex = count; 
      if (count > 0) 
      { 
       count--; 
       return arr[popIndex]; 
      } 
      else 
      { 
       return arr[count]; 
      } 

     } 

     public void Push(T item) 
     { 

      count++; 
      if (count == arr.Length) 
      { 
       Array.Resize(ref arr, arr.Length + 1); 
      } 

      arr[count] = item; 


     } 

     public void Clear() 
     { 
      count = 0; 

     } 

     public T Peek() 
     { 
      return arr[count]; 
     } 

     public int Count 
     { 
      get 
      { 
       return count; 
      } 
     } 

     public IEnumerator<T> GetEnumerator() 
     { 
      return new MyEnumerator(this); 
     } 

     IEnumerator IEnumerable.GetEnumerator() 
     { 
      return new MyEnumerator(this); 
     } 

     public class MyEnumerator : IEnumerator<T> 
     { 
      private int position; 
      private CustomStack<T> stack; 

      public MyEnumerator(CustomStack<T> stack) 
      { 
       this.stack = stack; 
       position = -1; 
      } 
      public void Dispose() 
      { 

      } 
      public void Reset() 
      { 
       position = -1; 
      } 

      public bool MoveNext() 
      { 
       position++; 
       return position < stack.Count; 
      } 

      Object IEnumerator.Current 
      { 
       get 
       { 
        return stack.arr[position]; 
       } 
      } 
      public T Current 
      { 
       get 
       { 
        return stack.arr[position]; 

       } 
      } 
     } 
    } 
} 
+1

您是否花費了精力進行調試?只需一步一步,看看會發生什麼。 – 2013-05-11 06:23:51

+0

此問題被關閉爲「太本地化」;我不同意這種評估。我看到有多人犯這個錯誤。 (修改迭代集合的一般錯誤,以及在迭代它時彈出堆棧的特定錯誤,並且僅得到一半元素) – 2013-05-14 17:07:10

回答

15

你正在做的事情,你需要永遠不會做:你是修改集合,而你是用一個枚舉迭代它。 (該foreach循環是用於分配枚舉一個語法糖。)

IEnumerable文檔實際上表明,一個像你實現拋出一個異常如果你的數據結構,同時列舉了修改。 (與List<T>嘗試它,你會看到,如果您添加或在列表中foreach被枚舉刪除項目列表中會拋出。)

那是你的問題的原因;你的數據結構不是被設計爲(1)被濫用時拋出,或者(2)被濫用時表現良好,因此當你濫用它時表現不好。

我的建議:如果這樣做會傷害你,那麼不要這樣做。在枚舉它的循環中不要修改集合。

相反,做出IsEmpty財產,寫你的循環:

while(!collection.IsEmpty) 
    Console.WriteLine(collection.Pop()); 

你是不是修改集合同時有在同一時間正在處理一個枚舉的方式。

你在這裏得到的具體問題是:position每次都在循環中增加。而count總是在減少。你說只有一半的項目正在計算。那麼,解決它。如果你有十個項目,位置從0開始,並一直持續到它大於通過循環計數,那麼每次...

position count 
0   10 
1   9 
2   8 
3   7 
4   6 
5   5 

,我們正在做的,我們只列舉了一半項目。

如果你想使你的收藏品中,面對強大的被修改而被迭代然後position具有當堆棧被壓入或彈出改變。即使計數正在變化,也不能每次盲目增加。制定正確的行爲非常棘手,這就是爲什麼文檔建議你簡單地拋出。

如果你想讓你的集合在被枚舉修改時拋出一個異常,訣竅是讓對象有一個叫做「版本號」的int。每次推送或彈出集合時,都要更改版本號。然後讓迭代器在迭代開始時獲取版本號的副本;如果它檢測到當前版本號與副本不同,則在枚舉過程中集合已被修改,並且您可以拋出集合修改的異常。

感謝您的有趣問題;我可能會把它作爲我博客中的一個例子,並且可能看看我是否可以編寫一個靜態分析器來檢測這種危險的修改。

+0

還要注意,您可以遵循'BlockingCollection'的模式並擁有'GetConsumingEnumerable'。關鍵的區別在於,你並沒有修改列舉它的'foreach'正文中的集合,你從枚舉器的定義中得到一個可枚舉的*,正在修改基礎集合。這樣的枚舉器甚至可以寫在外部:'static IEnumerable GetConsumingEnumerable (This CustomStack s){while(!s.IsEmpty)yield return s.Pop();}' – Servy 2013-05-14 16:52:26

+0

有什麼優點和缺點一次枚舉類型實現一個名爲'GetEnumerator'的方法,以便允許'foreach',而不執行'IEnumerable '?能夠「foreach」只能枚舉一次的東西肯定是方便的,但是'IEnumerable '通常預計會允許重複列舉。 – supercat 2013-05-14 20:46:52

+0

您不必檢查增加量,但也可以製作數據副本。我在創建枚舉器時使用這個。 – 2013-08-21 14:18:16