C# 70-536 – Chapter 4
這章最主要是討論「集合(Collection)」的使用。在開發的時候最常用的就是集合了,所以非常重要…
ArrayList是一個簡單且無序的集合,可以儲存任何型別(Object),如果置放實質型別則會被Boxing
可以單獨加入一個項目,或是實作ICollection介面的物件。支援排序方法,也可以自訂排序方式,但需實做IComparer介面。
**若用foreach取用直接轉型時,轉型失敗會出現Exception
string[] strArray = { "Shelly", "Alonstar" };
ArrayList al = new ArrayList();
//al.Add(1); //Boxing
//新增
al.Add("1");
al.Insert(0, "3");
al.AddRange(strArray);
//插入
al.InsertRange(1, strArray);
al.Sort(new CaseInsensitiveComparer()); //CaseInsensitiveComparer表不分大小寫排序,繼承自IComparer.
foreach (string item in al) //利用foreach存取,可以直接轉型
{
Console.WriteLine(item);
}
IEnumerator enumerator = al.GetEnumerator(); //利用列舉子存取,所有的泛型也都可以~
while (enumerator.MoveNext())
{
Console.WriteLine(enumerator.Current);
}
佇列集合有二種:Queue (先進先出FIFO) & Stack (後進先出LIFO)
//FIFO Queue
Queue q = new Queue();
q.Enqueue("First");
q.Enqueue("Second");
q.Enqueue("Third");
Console.WriteLine("Queue has {0} numbers", q.Count);
while (q.Count > 0)
{
Console.WriteLine("q.Dequeue : " + q.Dequeue());
}
Console.WriteLine("Queue has {0} numbers", q.Count);
/* Output
* Queue has 3 numbers
* q.Dequeue : First
* q.Dequeue : Second
* q.Dequeue : Third
* Queue has 0 numbers
*/
//LIFO Stack
Stack t = new Stack();
t.Push("First");
t.Push("Second");
t.Push("Third");
Console.WriteLine("Stack has {0} numbers", t.Count);
while (t.Count > 0)
{
Console.WriteLine("t.pop :" + t.Pop());
}
Console.WriteLine("Stack has {0} numbers", t.Count);
/* Output
* Stack has 3 numbers
* t.pop :Third
* t.pop :Second
* t.pop :First
* Stack has 0 numbers
*/
字典(Dictionary):是用來儲存Key / Value (DictionaryEntry)對應的集合。
最基本的型別是Hashtable,只能夠用key值來存取,無法用index,且以key的HashCode來判斷是否為使用同一key值,並進行Equal比較是否一致。如果key重覆會直接覆寫。
如果使用物件當key,每個物件在建立的時候都會有不同的HashCode,如果要讓Hashtable判斷相同,則需覆寫GetHashCode 及 Equal方法。或是實做IEqualityComparer,來指定比較的方式。
//Hashtable只可以用key值來存取
Hashtable hs = new Hashtable();
hs["First"] = "first";
hs["First"] = "sencod";
Console.WriteLine("First".GetHashCode());
Console.WriteLine("First".GetHashCode());
Console.WriteLine(hs["First"]);
/* Output
* -1920739956
* -1920739956
* sencod hs["First"]被覆寫,因為"First"的雜湊碼(GetHashCode)相同
*/
hs.Clear();
Publisher p1 = new Publisher();
Publisher p2 = new Publisher();
hs[p1] = "third";
hs[p2] = "forth";
Console.WriteLine(p1.GetHashCode());
Console.WriteLine(p2.GetHashCode());
Console.WriteLine(hs.Count);
/* Output
* 45653674
* 41149443
* 2
*/
//HashTable 以hashcode為key,用object.Equals方法判斷是否相同
//如果要讓hasttable認為是相同的,要同時覆寫GetHasCode & Equals方法
hs.Clear();
Fish f1 = new Fish("Herring");
Fish f2 = new Fish("Herring");
hs[f1] = "Hello";
hs[f2] = "Hello";
Console.WriteLine(f1.GetHashCode());
Console.WriteLine(f2.GetHashCode());
Console.WriteLine(hs.Count);
/* Output
* -153736088
* -153736088
* 1
*/
//指定key的比較方式, 必需實做IEqualityComparer
Hashtable hs2 = new Hashtable(new InsensitiveComparer());
hs2["One"] = "Hello";
hs2["one"] = "Hello";
Console.WriteLine("One hashcode = {0}, one hash = {1}", "One".GetHashCode(), "one".GetHashCode());
Console.WriteLine("hs2 has {0} members", hs2.Count);
/* Output
* One hashcode = 1700053362, one hash = 1700053330
* hs2 has 1 members
*/
類別Fish,覆寫GetHashCode & Equal
class Fish
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
public Fish(string name)
{
_name = name;
}
public override int GetHashCode()
{
return _name.GetHashCode();
}
public override bool Equals(object obj)
{
Fish f = obj as Fish;
if (f == null) return false;
return f.Name == _name;
}
}
實作IEqualityComparer
/// <summary>
/// HashTable 實做IEqualityComparer類別,來比較key是否相同
/// </summary>
public class InsensitiveComparer : IEqualityComparer
{
CaseInsensitiveComparer _comparer = new CaseInsensitiveComparer();
#region IEqualityComparer 成員
bool IEqualityComparer.Equals(object x, object y)
{
if (_comparer.Compare(x, y) == 0)
return true;
else
return false;
}
int IEqualityComparer.GetHashCode(object obj)
{
return obj.ToString().ToLowerInvariant().GetHashCode();
}
#endregion
}
SortedList 也是一個字典類別,但不同的是可以利用index存取,在加入項目的時候會自動排序。可以自訂排序方法,但需實作IComparer介面。
**另外CollectionsUtil提供了二個方法可以建立不分大小寫的Hashtable & SortedList,.Net也有內建幾個已經實做IComparer or IEqualityComparer的類別可以使用,例:StringComparer, CaseInsensitiveComparer
//SortedList 可用index & key 存取, 加入時直接排序
//自訂排序方式可實做Icomparer
SortedList sd = new SortedList();
sd.Add(3, "Hello1");
sd.Add(2, "Hello2");
sd.Add(1, "Hello3");
for (int i = 0; i < sd.Count; i++)
{
Console.WriteLine("for i={0} : {1}", i, sd.GetByIndex(i));
}
foreach (DictionaryEntry entry in sd)
{
Console.WriteLine("foreach entry={0} : {1}", entry.Key, entry.Value);
}
/* Output
* for i=0 : Hello3
* for i=1 : Hello2
* for i=2 : Hello1
* foreach entry=1 : Hello3
* foreach entry=2 : Hello2
* foreach entry=3 : Hello1
*/
但如果你需要使用index & key存取,但又不希望他自動排序,可以使用OrderedDictionary
OrderedDictionary od = new OrderedDictionary();
od.Add(3, "Hello1");
od.Add(2, "Hello2");
od.Add(1, "Hello3");
for (int i = 0; i < od.Count; i++)
{
Console.WriteLine("for i={0} : {1}", i, od[i]);
}
/* Output
* for i=0 : Hello1
* for i=1 : Hello2
* for i=2 : Hello3
*/
//不分大小寫的Hashtable & SortedList
Hashtable inTable = CollectionsUtil.CreateCaseInsensitiveHashtable();
SortedList inList = CollectionsUtil.CreateCaseInsensitiveSortedList();
//不受文化特性影響區分大小寫
Hashtable hash = new Hashtable(StringComparer.InvariantCulture);
SortedList list = new SortedList(StringComparer.InvariantCulture);
位元集合
BitArray是一個用來儲存Boolean值,且可以調整大小的集合(調整Length屬性)。還可以直接使用邏輯運算。
BitVector32和BitArray有點不同,他的大小是固定32bit,但是可以直接調整每一個bit的值,可以用Data屬性直接回傳值。
//BitArray 只存boolean & 可調整長度
BitArray bitArray = new BitArray(3);
bitArray[0] = true;
bitArray[1] = false;
bitArray[2] = true;
bitArray.Length = 4; //增加長度
bitArray[3] = true;
BitArray bits1 = new BitArray(3);
bits1[1] = true;
for (int i = 0; i < bits1.Length; i++)
{
Console.WriteLine("b1[{1}]:{0}", bits1[i], i);
}
BitArray b2 = new BitArray(3);
b2[0] = true;
for (int i = 0; i < b2.Length; i++)
{
Console.WriteLine("b2[{1}]:{0}", b2[i], i);
}
//執行運算會修當改前的陣列
BitArray xorBits = bits1.Xor(b2);
for (int i = 0; i < xorBits.Length; i++)
{
Console.WriteLine("b1[{3}]:{0}, b2[{3}]:{1}, b1[{3}] xor b2[{3}] = {2}", bits1[i], b2[i], xorBits[i], i);
}
/*
* b1[0]:False
* b1[1]:True
* b1[2]:False
* b2[0]:True
* b2[1]:False
* b2[2]:False
* 做xor運算後.....
* b1[0]:True, b2[0]:True, b1[0] xor b2[0] = True
* b1[1]:True, b2[1]:False, b1[1] xor b2[1] = True
* b1[2]:False, b2[2]:False, b1[2] xor b2[2] = False
*/
//BitVector32 可用來操作一個32-bit整數裡的位元,只能固定在32bit
//包裝單一位元
BitVector32 bitV = new BitVector32(0);
int firstBit = BitVector32.CreateMask();
int secondBit = BitVector32.CreateMask(firstBit);
int thirdBit = BitVector32.CreateMask(secondBit);
Console.WriteLine("firstBit:" + firstBit);
Console.WriteLine("secondBit:" + secondBit);
Console.WriteLine("thirdBit:" + thirdBit);
Console.WriteLine("before bitV.Data:" + bitV.Data);
Console.WriteLine(bitV);
bitV[firstBit] = true;
bitV[secondBit] = true;
bitV[thirdBit] = true;
Console.WriteLine("after bitV.Data:" + bitV.Data);
Console.WriteLine(bitV);
//利用CreateSection來包裝數值(書上的範例答案有誤)
BitVector32.Section firstSection = BitVector32.CreateSection(10);
BitVector32.Section secondSection = BitVector32.CreateSection(50, firstSection);
BitVector32.Section thirdSection = BitVector32.CreateSection(500, secondSection);
BitVector32 bit32 = new BitVector32(0);
bit32[firstSection] = 10;
bit32[secondSection] = 1;
bit32[thirdSection] = 192;
Console.WriteLine("firstsection:" + bit32[firstSection]);
Console.WriteLine("secondSection:" + bit32[secondSection]);
Console.WriteLine("thirdBit:" + bit32[thirdSection]);
Console.WriteLine("bit32.Data:" + bit32.Data);
Console.WriteLine(bit32);
/* Output
* firstsection:10
* secondSection:1
* thirdBit:192
* bit32.Data:196634
* BitVector32{00000000000000110000000000011010}
*/
特別的字串集合:StringCollection & StringDictioyary & NameValueCollection
StringCollection & StringDictioyary 用法和ArrayList是類似的,但是key和value只能使用字串。最裡面的Data還是使用ArrayList包裝,所以還是有使用到Boxing…
但NameValueCollection有個比較特別的地方:當key值相同時,value他會自動組合成字串陣列。
//如同 ArrayList ,但只能放字串
StringCollection sc = new StringCollection();
sc.Add("First");
sc.Add("Second");
//key和值都只能是字串
StringDictionary sd = new StringDictionary();
sd.Add("First", "Hello1");
sd.Add("Second", "Hello2");
//sd.Add("first", "Hello3"); key不分大小寫,重覆直接exception
//key 不分大小寫,重覆的會變陣列
NameValueCollection nvc = new NameValueCollection();
nvc.Add("First", "Hello1");
nvc.Add("first", "Hello2");
nvc.Add("Second", "Hello3");
nvc.Add("First", "Hello4");
for (int i = 0; i < nvc.Count; i++)
{
Console.WriteLine("key:{0} values:{1}", nvc.Keys[i], nvc[i]);
}
/* Output
* key:First values:Hello1,Hello2,Hello4
* key:Second values:Hello3
*/
在.NET Framework2.0中,為了能更為便利的使用物件,減少Boxing問題,在集合中也增加了泛型方法。以下是各種集合對應到的泛型集合方法。使用的方式一樣,只是在宣告的時候必需特別指定使用的型別,且不能改變。而在Dictionary的DictionaryEntry則改用了KeyValuePair新泛型型別。而比較運算則需實作泛型的IComparer<T>及IEqualityComparer<T>
**開發2.0以上版本,最好都使用泛型集合較為安全
使用Dictionary<TKey, TValue>泛型集合:
Dictionary<int, string> dict = new Dictionary<int, string>();
dict.Add(3, "Three");
dict.Add(2, "Second");
dict.Add(1, "One");
//利用KeyValuePair存取
foreach (KeyValuePair<int, string> item in dict)
{
Console.WriteLine("key:{0}, value:{1}", item.Key, item.Value);
}
另外還新增了一個比較特別的佇列集合:LinkedList,可以使用相鄰的方式來存取(Next, Pervious),而單一項目則以LinkedListNode類別來存取。
LinkedList<string> links = new LinkedList<string>();
LinkedListNode<string> root = links.AddFirst("Root");
LinkedListNode<string> home1 = links.AddAfter(root, "Home1");
LinkedListNode<string> home11 = links.AddAfter(home1, "Home11");
Console.WriteLine(root.Value);
Console.WriteLine(root.Next.Value);
Console.WriteLine(root.Next.Next.Value);
Console.WriteLine(root.List.First.Value);
Console.WriteLine(root.List.Last.Value);
/*
* root.Value = Root
* root.Next.Value = Home1
* root.Next.Next.Value = Home11
* root.List.First.Value = Root
* root.List.Last.Value = Home11
*/