How Generics Work
Generics簡述
Generics 是.Net2.0才加入的功能。是一種型別參數的概念,也就是當class或method在設計時,可以先擱置型別,直到用戶端程式要使用時再行處理型別。 目的在於避免run time時需要boxing/unboxing的情況。泛型可以用在.Net中的許多地方,但是最常見的就屬泛型集合類別。
下列程式碼分別以整數型別和泛型設計一個串列類別,可以清楚的看到,差別就在int與<T>的不同而已。
public class IntList : IEnumerable { private ArrayList list = new ArrayList(); public void Add(int val) { list.Add(val); } public int this[int index] { get { return (int)list[index]; } } } IntList myList1 = new IntList(); myList1.Add(4); myList1.Add(5); myList1.Add(6); foreach (int num in myList1) { Console.WriteLine(num.ToString()); } | public class GenList<T> : IEnumerable { private ArrayList list = new ArrayList(); public void Add(T val) { list.Add(val); } public T this[int index] { get { return <T> list[index]; } } } GenList<int> myList2 = new GenList<int>(); myList2.Add(4); myList2.Add(5); myList2.Add(6); foreach (int num in myList2) { Console.WriteLine(num.ToString()); } |
Generics優點
- 重複使用:只設計一份程式碼,就可以適用各種型別。
- 型別安全:若有不合法的型別資料,在編譯階段就可以發覺。
- 效率提高:執行階段,避免不必要的boxing/unboxing,效能自然提高。
何時使用泛型集合
當集合元素為實值型別時,泛型集合型別通常要比對應的非泛型集合型別有較理想的效能 (也優於衍生自非泛型基底集合型別的型別),因為有了泛型就不需要將這些元素進行 Box 處理。
泛型委派
List<T> 類別有一些方法可以接受泛型委派,這是非泛型集合型別中所沒有的。
- IComparer<T>:指定自己的排序及搜尋介面
- Predicate<T>:指定搜尋清單之方法
- Action<T>:表示在清單的每一個元素上執行的方法
- Converter<TInput, TOutput>:定義型別之間的轉換
Improving Safety and Performance
大部分的集合類別,都有相對應的泛型存在。另外還有幾個新集合只適用於泛型型別。下表為一些集合型別,以及它們所對應的泛型型別
Equivalent Generic Types
Type | Generic Type |
---|---|
ArrayList | List<T> |
Queue | Queue<T> |
Stack | Stack<T> |
SortedList | SortedList<T,U> |
N/A | SortedDictionary<> |
Hashtable | Dictionary<T,U> |
ListDictionary | |
HybridDictionary | |
OrderedDictionary | |
NameValueCollection | |
DictionaryEntry | NameValuePair<> |
stringCollection | List<string> |
stringDictionary | Dictionary<string> |
N/A | LinkedList<> |
CollectionBase | Collection<T> |
ReadOnlyCollectionBase | ReadOnlyCollection<T> |
List<T> 類別
List<T> 類別是 ArrayList 類別的泛型對應項,具有以下幾項特色:。
- 可以直接使用 index 存取這個集合中的元素。index 值從零起始。
- 提供搜尋、排序和管理清單的方法。
- 使用一個可動態增減的陣列空間,以實作 IList<T> 泛型介面。
- 集合中的項目並不保證已經經過排序。若要執行類似 List<T>.BinarySearch 運算之前,一定要對先 List<T> 進行排序。
- 集合接受 null 做為參考型別的有效值,並允許重複的項目。
List<int> intList = new List<int>(); // 加入項目 intList.Add(5); intList.Add(2); intList.Add(3); // 更新第2項 intList[1] = 4; // 使用 indexer 取值 for (int i = 0; i < intList.Count; i++) { Console.WriteLine(intList[i]); } // 排序 intList.Sort(); // 使用 foreach 巡訪取值 foreach (int i in intList) { Console.WriteLine(i); }
class Product{} class Product1 : Product{} class Product2 : Product{} class Product3{} private void button3_Click(object sender, EventArgs e) { List<Product> product_list = new List<Product>(); product_list.Add(new Product()); product_list.Add(new Product1()); product_list.Add(new Product2()); //product_list.Add(new Product3()); product_list.Add(null); }
另外,值得一提的是,這個 List<T> 類別,在做 Sort() 方法時,支援泛型委派。
//泛型委派(generic delegates) //They are just like generic classes or structures, but generic parameters //are used only to define the calling convention of the delegate. static int SortByStringLength(string x, string y) { return y.Length - x.Length; } private void button3_Click(object sender, EventArgs e) { List<string> myList = new List<string>(); myList.Add("orange"); myList.Add("apple"); myList.Add("tangerine"); myList.Sort(); foreach (string item in myList) { Console.WriteLine(item); } //原始排序: apple orange tangerine myList.Sort(SortByStringLength); //自行定義一個泛型委派,依字串長度排序 foreach (string item in myList) { Console.WriteLine(item); } //自訂排序: tangerine orange apple }
Stack<T> 類別
Queue<string> q = new Queue<string>(); q.Enqueue("First"); q.Enqueue("Second"); q.Enqueue("Third"); q.Enqueue("Fourth"); while (q.Count > 0) { Console.WriteLine(q.Dequeue()); } Stack<string> s = new Stack<string>(); s.Push("First"); s.Push("Second"); s.Push("Third"); s.Push("Fourth"); while (s.Count > 0) { Console.WriteLine(s.Pop()); }
Dictionary<T,U> 類別
[SerializableAttribute] [ComVisibleAttribute(false)] public class Dictionary<TKey, TValue> : IDictionary<TKey, TValue>, ICollection<KeyValuePair<TKey, TValue>>, IEnumerable<KeyValuePair<TKey, TValue>>, IDictionary, ICollection, IEnumerable, ISerializable, IDeserializationCallback }
- Dictionary<TKey, TValue> 是可用來儲存 key/value 的泛型字典集合。
- TKey 不能重複,它是由 equality 的實作來判斷是否相等,預設為 EqualityComparer
.Default 。 - Dictionary<TKey, TValue> 的容量會依需要藉由重新配置內部陣列而自動增加。
- Dictionary<TKey, TValue> 的每一個項目都視為表示一個值及其索引鍵的 KeyValuePair<TKey, TValue> 結構。
Dictionary<string, string> dict = new Dictionary<string, string>(); dict["D3"] = "Three"; dict["D4"] = "Four"; dict["D1"] = "One"; dict["D2"] = "Two"; string str = dict["D2"]; //使用 key 取值 // 使用 key 取值 foreach (string key in dict.Keys) { Console.WriteLine("{0}, {1}", key, dict[key]); } // D3 = Three // D4 = Four // D1 = One // D2 = Two // 集合保持原來的順序 }
泛型與非泛型的 Dictionary 類別,最大的差異是 Dictionary<T,U> 不是使用 DictionaryEntry 物件來保存key/value,而是使用新的泛型型別 KeyValuePair<TKey, TValue> 來保存。
// 使用 foreach 巡訪,並利用新的泛型型別 KeyValuePair取值 foreach (KeyValuePair<int, string> i in dict) { Console.WriteLine("{0} = {1}", i.Key, i.Value); }
SortedDictionary<T,U> 類別
SortedList<T,U> and SortedDictionary<T,U> 這二個泛型集合都類似 Dictionary<T,U> ,相似處有:
- 二者都是二進位搜尋樹狀目錄。
- 集合中的項目都會根據 key 值做排序
這二個泛型集合主要的不同在於,它們插入和移除項目的實作方法不同,所以在記憶體的使用上和執行速度上會有所不同。
- SortedList<T,U> 使用的記憶體少於 SortedDictionary<T,U>。
- 對於未排序的資料,執行插入和移除操作時,SortedDictionary<T,U> 使用 O(log n) 優於 SortedList<T,U> 的 O(n)。
- 如果從排序的資料一次全部填入清單,則 SortedLis<T,U> 快於 SortedDictionary<T,U>。
另外,在使用上,二者的 Values 屬性雖然都是回傳值的集合,但回傳的型別不同。SortedList<T,U> 回傳的是 IList<T> ,SortedDictionary<T,U> 回傳的是 SortedDictionary<T, U>.ValueCollection 。
SortedList<string, string> sortedList = new SortedList<string, string>(); sortedList.Add("111", "apple"); sortedList.Add("333", "book"); sortedList.Add("222", "cherry"); sortedList.Add("444", "dog"); sortedList.Remove("111"); sortedList.Add("111", "apple"); foreach (KeyValuePair<string, string> item in sortedList) { Console.WriteLine("{0} {1}", item.Key, item.Value); } // 111 apple // 222 cherry // 333 book // 444 dog foreach (KeyValuePair<string, string> item in sortedList) { Console.WriteLine("{0} {1}", item.Key, item.Value); } // 111 apple // 222 cherry // 333 book // 444 dog foreach (string value in sortedList.Values) { Console.WriteLine(value.ToString()); } // apple // cherry // book // dog for (int i = 0; i < sortedList.Count; i++) { Console.WriteLine("{0} {1}", sortedList.Keys[i], sortedList.Values[i]); } // 111 apple // 222 cherry // 333 book // 444 dog
// 用法結果都同 SortedList SortedDictionary<string, string> sortedDict = new SortedDictionary<string, string>(); sortedDict.Add("111", "apple"); sortedDict.Add("333", "book"); sortedDict.Add("222", "cherry"); sortedDict.Add("444", "dog"); sortedDict.Remove("111"); sortedDict.Add("111", "apple"); foreach (KeyValuePair<string, string> item in sortedDict) { Console.WriteLine("{0} {1}", item.Key, item.Value); } foreach (KeyValuePair<string, string> item in sortedDict) { Console.WriteLine("{0} {1}", item.Key, item.Value); } foreach (string value in sortedDict.Values) { Console.WriteLine(value.ToString()); } //不支援索引鍵 //for (int i = 0; i < sortedDict.Count; i++) //{ // Console.WriteLine("{0} {1}", sortedList.Keys[i], sortedList.Values[i]); //}
LinkedList<T> 類別
- LinkedList<T> 表示雙向連結串列 (Doubly Linked List)。
- LinkedList<T> 它支援列舉值並實作 ICollection 介面,與 .NET Framework 中的其他集合類別一致。
- LinkedList<T> 物件中每一個節點都是 LinkedListNode<T> 型別。
- LinkedList<T> 是雙向連結串列,可以由 Next 屬性取得下一個節點, Previous 屬性取得前一個節點
底下是 LinkedList<T> 類別的組成成員
public class LinkedList<T> : ICollection<T>, IEnumerable<T>, ICollection, IEnumerable, ISerializable, IDeserializationCallback { public LinkedListNode<T> First { get; } //第一個節點 public LinkedListNode<T> Last { get; } //最後一個節點 public void AddFirst(LinkedListNode<T> node); //開頭加入指定的新節點。 public void AddLast(LinkedListNode<T> node); //結尾加入指定的新節點。 public void AddBefore(LinkedListNode<T> node, LinkedListNode<T> newNode); //現有節點前加入指定的新節點。 public void AddAfter(LinkedListNode<T> node, LinkedListNode<T> newNode); //現有節點後加入指定的新節點 public LinkedListNode<T> AddFirst(T value); //開頭加入包含指定值的新節點。 public LinkedListNode<T> AddLast(T value); //結尾加入包含指定值的新節點。 public LinkedListNode<T> AddBefore(LinkedListNode<T> node, T value); //現有節點前加入包含指定值的新節點。 public LinkedListNode<T> AddAfter(LinkedListNode<T> node, T value); //現有節點後加入包含指定值的新節點 public LinkedListNode<T> Find(T value); //尋找包含指定值的第一個節點。 public LinkedListNode<T> FindLast(T value); //尋找包含指定值的最後一個節點。 public void Remove(LinkedListNode<T> node); //移除指定的節點。 public bool Remove(T value); //移除第一次出現的指定值。 public void RemoveFirst(); //移除 LinkedList<T> 開頭的節點。 public void RemoveLast(); //移除 LinkedList<T> 結尾的節點。 public void Clear(); public bool Contains(T value); public void CopyTo(T[] array, int index); public LinkedList<T>.Enumerator GetEnumerator(); public virtual void GetObjectData(SerializationInfo info, StreamingContext context); public virtual void OnDeserialization(object sender); }
LinkedList<string> links = new LinkedList<string>(); LinkedListNode<string> last = links.AddLast("999"); //加一節點至Last LinkedListNode<string> first = links.AddFirst("111"); //加一節點至First LinkedListNode<string> second = links.AddBefore(last, "222"); //在last節點前面加入一個節點 links.AddAfter(second, "333"); //在second節點後面加入一個節點 foreach (string s in links) { Console.WriteLine(s); } //111 //222 //333 //999
Generic Collection Class Structure
泛型集合介面 Generic Collection Interfaces
一般集合都有支援相關的介面 (interfaces),如 ICollection, IDictionary, IEnumerable 。 泛型集合除了同樣支援這些介面外,而且額外支援泛型介面 (generic interfaces),以提供型別安全。
List<string> stringList = new List<string>(); // ... // List<T> 支援 非泛型的 IList 介面 IList theList = (IList)stringList; object firstItem = theList[0]; // List<T> 也支援 泛型的 IList<T> 介面 //若使用泛型的IList介面,可增加型別安全 IList<string> typeSafeList = (IList<string>)stringList; string firststring = typeSafeList[0];
泛型集合列舉 Generic Collection Enumerators
泛型集合也支援列舉 (iterating),透過 GetEnumerator 方法,可以取得 enumerator 。 這個回傳的 enumerator 也是個泛型,其型別與它的集合型別相同。
List<string> myList = new List<string>(); myList.Add("111"); myList.Add("222"); List<string>.Enumerator enumerator = myList.GetEnumerator(); while (enumerator.MoveNext()) { string s = enumerator.Current; Console.WriteLine(s); } //111 //222 foreach (string s in myList) { Console.WriteLine(s); } //111 //222
泛型比較類別 Comparisons
Comparer<T> 與 EqualityCompare<T> 是泛型的二個基底類別,這二個基底類別分別實作了 ICompare<T> 與 IEqualityComparer<T> 介面。 因此,若需要實作自已的比較邏輯,可以繼承這些基底類別,覆寫基底類別的抽象方法,以執行自已的比較邏輯。
class MyComparer<T>; : Comparer<T> { public override int Compare(T x, T y) { return x.GetHashCode() - y.GetHashCode(); } } private void button8_Click(object sender, EventArgs e) { MyComparer<string> myComp = new MyComparer<string>(); SortedList<string, int> sortList = new SortedList<string, int>(myComp); sortList["Four"] = 4; sortList["Two"] = 2; sortList["Three"] = 3; foreach (KeyValuePair<string, int> i in sortList) { Console.WriteLine(i); } }
沒有留言:
張貼留言