.Net為我們提供了眾多的泛型集合。比如,Stack<T>先進後出,Queue<T>先進先出,List<T>集合元素可排序,支持索引,LinkedList<T>,雙向鏈表的泛型實現,不支持索引;ISet<T>不允許被復制,他有2個實現,一個是HashSet<T>,不維持集合元素的排序,另一個是SortedSet<T>,支持集合元素的排序;IDictionary<TKey, TValue>是一個字典集合的泛型接口,SortedList<TKey,TValue>實現了IDictionary<TKey, TValue>,但同時也是集合,維持集合元素的排序,支持按鍵或按值索引。
本篇體驗Stack<T>的用法。
基本用法
Stack<T>是Stack的泛型實現,提供了若干方法和屬性,比如入棧、出棧、查看棧頂元素,查看棧內集合元素數量,等等。棧的最大特點是先進後出,可以把棧想像成一堆疊起來的盤子,入棧就是把一個個盤子放到最上面,出棧就是從最上面把盤子拿掉。用法比較簡單:
class Program{static void Main(string[] args){var customer1 = new Customer() {ID = 1, Name = "張三", Gender = "男"};var customer2 = new Customer() { ID = 2, Name = "李四", Gender = "男" };Stack<Customer> stackCustomers = new Stack<Customer>();//入棧stackCustomers.Push(customer1);stackCustomers.Push(customer2);//查看棧頂元素Customer topCustomer = stackCustomers.Peek();Console.WriteLine("棧頂元素是:" + topCustomer.Name);//遍歷所有棧內元素foreach (var customer in stackCustomers){Console.WriteLine("id is {0},name is {1}", customer.ID, customer.Name);}//出棧Customer outCustomer = stackCustomers.Pop();Console.WriteLine("正在出棧的是:" + outCustomer.Name);Console.WriteLine("當前棧內元素數量為:" + stackCustomers.Count);Console.ReadKey();}}public class Customer{public int ID { get; set; }public string Name { get; set; }public string Gender { get; set; }}
臨摹一個泛型Stack<T>
泛型Stack類內部維護這一個泛型數組和索引指針,且指針的初始位置是-1。
入棧就是把指針往前提一位,並把入棧元素賦值給該棧內位置。另外,入棧要考慮是否達到容量上限,如果達到就要給數組擴容。
出棧就是讓當前棧位置的元素值為入棧類型的默認值,並大指針後退一位。
獲取棧頂元素就是獲取棧當前索引位置對應的元素。
public class MyStack<T>{//維護T類型的數組private T[] _elements;protected T[] Elements{get { return _elements; }set { _elements = value; }}public MyStack(){_capacity = 5;//初始值Elements = new T[Capacity];}public MyStack(int capacity){Capacity = capacity;Elements = new T[Capacity];}//指針private int _index = -1;public int Index{get { return _index; }set { _index = value; }}//容量private int _capacity;public int Capacity{get { return _capacity; }set { _capacity = value; }}//長度=索引+1public int Length{get { return Index + 1; }}//入棧public void Push(T element){if (this.Length == Capacity){IncreaseCapacity();}Index++;Elements[Index] = element;}//出棧public T Pop(){if (this.Length < 1){throw new InvalidOperationException("棧內已空");}T element = Elements[Index];//原先位置元素變成默認值Elements[Index] = default(T);//索引減一Index--;return element;}//獲取棧頂元素public T Peek(){if (this.Length < 1){throw new InvalidOperationException("棧內已空");}return Elements[Index];}private void IncreaseCapacity(){Capacity++;Capacity *= 2;//創建新的T類型數組T[] newElements = new T[Capacity];//把原先的數組復制到新的數組中來Array.Copy(Elements, newElements, Elements.Length);Elements = newElements;}}
現在,在客戶端,實施一系列的入棧和出棧操作。
static void Main(string[] args){//創建泛型Stack實例MyStack<int> myStack = new MyStack<int>();//遍歷10次入棧for (int i = 0; i < 10; i++){Console.WriteLine(i + "開始入棧");myStack.Push(i);Console.WriteLine("當前棧的長度是:" + myStack.Length);}//遍歷10次出棧for (int i = 0; i < 10; i++){Console.WriteLine("當前出棧的是" + myStack.Peek());myStack.Pop();Console.WriteLine("當前棧的長度是:" + myStack.Length);}//所有出棧結束,再查看棧頂元素拋異常try{myStack.Peek();}catch (InvalidOperationException ex){Console.WriteLine(ex.Message);}//所有出棧結束,再出棧拋異常try{myStack.Pop();}catch (InvalidOperationException ex){Console.WriteLine(ex.Message);}Console.ReadKey();}
其實,泛型Stack<T>的內部也是維護著一個數組,數組的容量是動態變化的,這一點很像List<T>,就像這裡提到的。
使用泛型Stack<T>實現"撤銷/重做"操作
首先,操作或撤銷操作是針對某種類型的撤銷或重做,提煉出一個接口。
public interface ICommand<T>{T Do(T input);T Undo(T input);}
假設,這裡想實現對整型數的"撤銷/重做"操作。
public class AddIntCommand : ICommand<int>{private int _value;public int Value{get { return _value; }set { _value = value; }}public AddIntCommand(){_value = 0;}public AddIntCommand(int value){_value = value;}//執行操作public int Do(int input){return input + _value;}//撤銷操作public int Undo(int input){return input - _value;}}
接下來,需要一個泛型類來管理所有撤銷或操作命令,把這些命令放在Stack<ICommand<T>>泛型集合中。
//使用泛型Stack實現撤銷或重做public class UndoRedoStack<T>{private Stack<ICommand<T>> _undo;//有關撤銷的泛型stackprivate Stack<ICommand<T>> _redo;//有關重做的泛型stackpublic UndoRedoStack(){Reset();}//記錄撤銷的數量public int UndoCount{get { return _undo.Count; }}//記錄重做的數量public int RedoCount{get { return _redo.Count; }}//恢復到出廠設置public void Reset(){_undo = new Stack<ICommand<T>>();_redo = new Stack<ICommand<T>>();}//執行操作public T Do(ICommand<T> cmd, T input){T output = cmd.Do(input);//把剛才的命令放入有關撤銷的stack中_undo.Push(cmd);//一旦啟動一個新命令,有關重做的stack清空_redo.Clear();return output;}//撤銷操作public T Undo(T input){if (_undo.Count > 0){//出棧ICommand<T> cmd = _undo.Pop();T output = cmd.Undo(input);_redo.Push(cmd);return output;}else{return input;}}//重做操作public T Redo(T input){if (_redo.Count > 0){ICommand<T> cmd = _redo.Pop();T output = cmd.Do(input);_undo.Push(cmd);return output;}else{return input;}}}
最後,在客戶端按如下調用:
static void Main(string[] args){UndoRedoStack<int> intCalulator = new UndoRedoStack<int>();int count = 0;count = intCalulator.Do(new AddIntCommand(10), count);count = intCalulator.Do(new AddIntCommand(20), count);Console.WriteLine("第一次計算的值為:{0}",count);//執行撤銷操作一次count = intCalulator.Undo(count);Console.WriteLine("第二次計算的值為:{0}",count);Console.ReadKey();}
參考資料:
http://brendan.enrick.com/post/Simple-C-Stack-Implementation
http://www.cambiaresearch.com/articles/82/generic-undoredo-stack-in-csharp