1. 程式人生 > >C#集合類(資料結構)

C#集合類(資料結構)

一、選擇資料結構

1)線性容器
List<T>陣列/Stack/Dequeue按需求模型選擇即可,LinkedList<T>是雙向連結串列增刪修改快.
需要有序陣列SortList<T>線性排序容器都可以;如果既需要查詢快又需要頻繁修改那麼可以用List<T>記錄索引,用LinkedList<T>儲存。

2)二叉樹型別容器
SortedDictionary<TKey,TValue>可以提供二叉樹型別插入刪除查詢都比較折中的鍵值對容器。
SortedSet<T>一個集合值型別的容器,比SortedDictionary<TKey,TValue>需要更少的空間。

3)雜湊表型別的容器
Dictionary<TKey,TValue>類似於C++/java中的HashMap實現,需要一個雜湊函式和一個相等判斷函式解決衝突,能夠有很高的插入和查詢效率。
HashSet<T>適合單個元素的集合操作型別。
ILookup<TKey,TValue>可以獲得一個鍵對應多個值的儲存型別,很有用的方面是從指定集合中篩選某種型別的資料集。

4)其它支援容器的介面類,委託,拓展方法和為了觀察,封裝位操作,封裝多執行緒操作的衍生型別容器
其它功能型別的介面及其委託,拓展方法:
ICollection<T>、IEnumerable<T>、IEquatable<T>、IComparer<T>、IComparable<T>、IEqualityComparer<T>、IFormattable介面
為了觀察,封裝位操作,封裝多執行緒操作的衍生型別容器:
ObservableCollection<T>,BitArray/BitVector32、IProducerConsumerCollection<T>介面

大多數集合類都在System.Collections.Generic名稱空間中,非泛型的System.Collections中已經很少用了。
特定集合類位於System.Collections.Specialized中,執行緒安全的集合類在System.Collections.Concurrent中。

集合類主要有:

二.ICollection<T>、IEnumerable<T>、IEquatable<T>、IComparer<T>、IComparable<T>、IEqualityComparer<T>、IFormattable介面。


三.Array、List<T>、佇列、棧、SortedList<TKey, TValue>、LinkedList<T>雙向連結串列

1.List<T>

List<T>在C#中實現也是陣列,動態陣列長度不夠會加倍。
不確定的陣列需要可變陣列用List<T>, 確定長度和數量多用Array, 不推薦用ArrayList因為新增的是object型別要裝箱和拆箱效能慢。
1)初始化:
初始化時候可以直接賦值,或者指定Count和Capacity來初始化。
對List<T>填充完資料以後可以用TrimExcess()方法去除不需要的容量,只有空白容量超過10%才會去除成功。
List<T>可以用AddRange新增多個元素。
2)訪問:
可以通過索引器訪問的集合類有:Array,List<T>,ArrayList, StringCollection, List<T>。
List<T>實現了IEnumerable<T>介面,所以也可以用foreach來訪問。

List<T>提供了ForEach方法,該方法用Action<T>作為引數。
public void ForEach(Action<T> action);
// 需要當前類中定義該委託的例項賦值給Action委託物件,也可以用Lambda表示式宣告該例項。
public delegate void Action<T> (T obj);

3)刪除
用RemoveAt效率較快,如果用Remove回先查詢值然後刪除回查詢引用,如果有重寫IEquatable<T>或者Object.Equals就會用這些方法判斷,否則
是用預設的引用比較,如果是相同的引用地址那麼就可以刪除成功。
刪除還可以用RemoveRange(index, count)來進行刪除。
如果要刪除指定特性的元素就可以用RemoveAll()方法,指定特性在Predicate<T>引數中指定。
要直接刪除所有的元素用ICollection<T>介面中定義的Clear()方法。

4)搜尋
IndexOf(),LastIndexOf(), FindIndex(), FindLastIndex(), Find(),FindLast().
判斷存在用Exists();
FindIndex()方法需要一個Predicate型別的引數。
public int FindIndex(Predicate<T> match);
需要給委託物件傳遞一個宣告的委託例項,例如:
public bool FindCountryPredicate(Racer racer)
{
   if( racer == null) return false;
   return racer.Country  == country;
}
將FindCountryPredicate傳入函式引數,即可,RemoveAll()中也需要傳遞入該委託例項。
Find(), FindIndex(), FindAll()都需要這樣的比較委託例項,委託例項的廣泛使用,如果只寫一次可以用Lambd表示式來寫,多次將其封裝為函式。

5)排序
排序Sort方法也需要傳遞比較大小的委託例項。
有大概三種比較大小的委託函式:
預設的是IComparable<T>一個other引數的比較委託。
IComparer<T>兩個引數的比較委託。
過載Sort方法,該方法需要一個Comparison<T>的委託例項。Comparison<T>的委託定義是public delegate int Comparison<T>(T x, T y);

可以呼叫Reverse()方法逆轉整個集合的排序。

6)型別轉換
使用List<T>類的ConvertAll<TOutput>()方法,可以把所有的集合型別轉換為另一種型別。
該TOutput委託的定義如下:
public sealed delegate TOutput Convert<TInput, TOutput>(TInput from);
需要定義一個委託例項,傳入該函式引數即可。

7)只讀集合
一般集合都是要支援讀寫的,但是有些比較特殊的應用需要給客戶提供一個只讀集合,那麼可以使用List<T>集合的AsReadOnly()方法就可以返回

一個ReadOnlyCollection<T>型別的物件。ReadOnlyCollection<T>和List<T>的差別只是不能寫排序刪除等,其它實現都一樣。

List簡單例子:

 static void Main()
        {
            var graham = new Racer(7, "Graham", "Hill", "UK", 14);
            var emerson = new Racer(13, "Emerson", "Fittipaldi", "Brazil", 14);
            var mario = new Racer(16, "Mario", "Andretti", "USA", 12);

            var racers = new List<Racer>(20) { graham, emerson, mario };

            racers.Add(new Racer(24, "Michael", "Schumacher", "Germany", 91));
            racers.Add(new Racer(27, "Mika", "Hakkinen", "Finland", 20));

            racers.AddRange(new Racer[] {
               new Racer(14, "Niki", "Lauda", "Austria", 25),
               new Racer(21, "Alain", "Prost", "France", 51)});

            var racers2 = new List<Racer>(new Racer[] {
               new Racer(12, "Jochen", "Rindt", "Austria", 6),
               new Racer(22, "Ayrton", "Senna", "Brazil", 41) });

            Console.WriteLine("-------racers------------");
            for( int i = 0; i < racers.Count; i++ )
            {
                Console.WriteLine(racers[i].ToString());
            }
            Console.WriteLine("-------racers2------------");
            for (int i = 0; i < racers2.Count; i++)
            {
                Console.WriteLine(racers2[i].ToString());
            }
        }

2.Queue<T>

先進先出,實現了ICollection和IEnumerable<T>介面,但沒有實現ICollection<T>介面,因此這個介面定義的Add()和Remove()方法不能用於佇列。
沒有實現List<T>介面,所以也不支援索引器訪問。

佇列中的常用方法,Count返回個數,Dequeue()進佇列,Enqueue出佇列並刪除佇列頭元素,Peek從佇列頭部讀取佇列但不刪除元素。
TrimExcess()可以清除Capacity中的大於10%時候的元素。

佇列Queue<T>的構造預設會4,8,16,32遞增的增加容量,.net 1.0版本的Queue卻是一開始就給了個32項的空陣列。
佇列例子:

public class DocumentManager
    {
        // readonly只是說這個佇列物件不寫(比如另一個物件拷貝給它),但是內部的元素是可以寫的
        private readonly Queue<Document> documentQueue = new Queue<Document>();

        public void AddDocument(Document doc)
        {
            lock (this)
            {
                documentQueue.Enqueue(doc);
            }
        }

        public Document GetDocument()
        {
            Document doc = null;
            lock (this)
            {
                doc = documentQueue.Dequeue();
            }
            return doc;
        }

        public bool IsDocumentAvailable
        {
            get
            {
                return documentQueue.Count > 0;
            }
        }
    }


using System;
using System.Threading;
namespace Wrox.ProCSharp.Collections
{
    public class ProcessDocuments
    {
        private DocumentManager documentManager;
        protected ProcessDocuments(DocumentManager dm)
        {
            documentManager = dm;
        }

        public static void Start(DocumentManager dm)
        {
            // ParameterizedThreadStart;
            // public delegate void ParameterizedThreadStart(object obj);
            // 直接這樣啟動一個執行緒了。
            new Thread( new ProcessDocuments(dm).Run ).Start();
        }

        protected void Run(object obj) // 這裡用不用object obj引數都可以,IL會做轉換為友object的。
        {
            while (true)
            {
                if (documentManager.IsDocumentAvailable)
                {
                    Document doc = documentManager.GetDocument();
                    Console.WriteLine("Processing document {0}", doc.Title);
                }
                Thread.Sleep(new Random().Next(20));
            }
        }
    }
}

using System;
using System.Threading;

namespace Wrox.ProCSharp.Collections
{
    class Program
    {
        static void Main()
        {
            var dm = new DocumentManager();
            ProcessDocuments.Start(dm);
            // Create documents and add them to the DocumentManager
            for (int i = 0; i < 1000; i++)
            {
                Document doc = new Document("Doc " + i.ToString(), "content");
                dm.AddDocument(doc);
                Console.WriteLine("Added document {0}", doc.Title);
                Thread.Sleep(new Random().Next(20));
            }
        }
    }
}

3.Stack<T>

後進先出,Count屬性,Push(),Pop()方法會刪除最頂元素,Peek()不會刪除,Contains()確定某個元素是否在棧中是則返回true.

using System;
using System.Collections.Generic;
namespace Wrox.ProCSharp.Collections
{
    class Program
    {
        static void Main()
        {
            var alphabet = new Stack<char>();
            alphabet.Push('A');
            alphabet.Push('B');
            alphabet.Push('C');

            Console.Write("First iteration: ");
            // 迭代遍歷用了迭代模式,不會刪除
            foreach (char item in alphabet)
            {
                Console.Write(item);
            }

            Console.Write("Second iteration: ");
            while (alphabet.Count > 0)
            {
                // Pop會刪除
                Console.Write(alphabet.Pop());
            }
            Console.WriteLine();
        }
    }
}

4.SortedList<TKey, TValue>

實現是基於陣列的列表,定義了單一任意型別的鍵和單一任意型別的值的資料結構,可以直接建立一個空的排序列表;或者過載建構函式可以定義列表容量和傳遞一個IComparer<TKey>介面的物件,該介面用於給列表中的元素排序。

為容器新增元素可以用Add()方法,也可以用索引下標賦值,相同鍵新增時候Add方法會丟擲異常不能覆蓋舊鍵,索引下標相同鍵時候會覆蓋舊鍵不拋異常。

訪問時候可以用集合迭代器元素的Key,Value屬性訪問鍵和值;也可以用集合的Values和Keys屬性來返回所有的鍵值屬性,類似C++中的STL map一樣。
通過索引器鍵值訪問元素時候,如果鍵不存在,那麼會丟擲異常,為了避免異常發生,可以用ContiansKey方法來判斷是否存在集合中,再用索引器訪問。
還可以直接用TryGetValue方法,嘗試獲得該鍵的值,如果不存在也不會丟擲異常。

例項:

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

namespace Wrox.ProCSharp.Collections
{
    class Program
    {
        static void Main(string[] args)
        {
            var books = new SortedList<string, string>();
            books.Add("C# 2008 Wrox Box", "978–0–470–047205–7");
            books.Add("Professional ASP.NET MVC 1.0", "978–0–470–38461–9");

            books["Beginning Visual C# 2008"] = "978–0–470-19135-4";
            books["Professional C# 2008"] = "978–0–470–19137–6";

            foreach (KeyValuePair<string, string> book in books)
            {
                Console.WriteLine("{0}, {1}", book.Key, book.Value);
            }

            foreach (string isbn in books.Values)
            {
                Console.WriteLine(isbn);
            }

            foreach (string title in books.Keys)
            {
                Console.WriteLine(title);
            }

            {
                string isbn;
                string title = "Professional C# 7.0";
                // 出現異常
                try
                {
                    isbn = books[title];
                }
               catch ( KeyNotFoundException err)
                {
                    Console.WriteLine("Exception " + err.ToString());
                }
              
                if (!books.TryGetValue(title, out isbn))
                {
                    Console.WriteLine("{0} not found", title);
                }
            }
        }
    }
}

5.LinkList<T>

LinkList<T>才是連結串列而且是雙向連結串列,前面的都是基於陣列的。連結串列典型的特徵就是插入刪除非常方便,但是查詢比較慢需要O(n)的查詢效率。
LinkedList<T>包含LinkedListNode<T>型別的元素,該節點定義了List、Next、Previous、Value,List屬性返回和節點相關的LinkedList<T>物件。LinkedList<T>可以訪問成員的第一個和最後一個元素(First和Last);可以在指定位置AddAfter()/AddBefore()/AddFirst()/AddLast()方法;刪除指定位置的元素Remove()/RemoveFirst()/RemoveLast()方法;查詢Find()和FindLast()。
LinkList<T>例項:
Document.cs:

namespace Wrox.ProCSharp.Collections
{
    public class Document
    {
        public string Title { get; private set; }
        public string Content { get; private set; }
        public byte Priority { get; private set; }

        public Document(string title, string content, byte priority = 0)
        {
            this.Title = title;
            this.Content = content;
            this.Priority = priority;
        }
    }

}

PriorityDocumentManager.cs:

using System;
using System.Collections.Generic;

namespace Wrox.ProCSharp.Collections
{
    public class PriorityDocumentManager
    {
        // 真正存放排序好的document的結構體
        private readonly LinkedList<Document> documentList;

        // priorities 0.9, 方便索引documentList本優先順序的最後一個元素;
        // 用一個List<T>陣列索引器,提高效率,找到陣列和連結串列之間平衡點的提高效能的好方法。
        private readonly List<LinkedListNode<Document>> priorityNodes;

        public PriorityDocumentManager()
        {
            documentList = new LinkedList<Document>();

            priorityNodes = new List<LinkedListNode<Document>>(10);
            for (int i = 0; i < 10; i++)
            {
                priorityNodes.Add(new LinkedListNode<Document>(null));
            }
        }

        // 對外介面
        public void AddDocument(Document d)
        {
            if (d == null) throw new ArgumentNullException("d");

            AddDocumentToPriorityNode(d, d.Priority);
        }

        private void AddDocumentToPriorityNode(Document doc, int priority)
        {
            // 外部呼叫要保證priority就是doc.priority, 否則後面會導致問題
            if (priority > 9 || priority < 0)
                throw new ArgumentException("Priority must be between 0 and 9");

            // 1.開始空或者中間空,遞迴會導致這裡不進來!=null(因為小優先順序的有元素時候)
            if (priorityNodes[priority].Value == null)
            {
                --priority;
                if (priority >= 0)
                {
                    // check for the next lower priority
                    // 2)遞迴是為了檢測小於優先順序的有沒有存在元素的,這時priority會小於doc.priority
                    AddDocumentToPriorityNode(doc, priority);
                }
                else // now no priority node exists with the same priority or lower
                // add the new document to the end
                {
                    // 1)第一次會進來或者當前priority以下的優先順序都沒有的情況也會進來
                    // 更小優先順序的都沒有,那麼它就是最小優先順序的
                    documentList.AddLast(doc);
                    // priorityNodes存放的時連結串列最後的那個元素,doc.Priority和documentList.Last上的優先順序一樣的
                    priorityNodes[doc.Priority] = documentList.Last;
                }
                return;
            }
            // 直接進來,或者遞迴進來,說明當前優先順序或者遞迴減到的優先順序有元素。
            else // a priority node exists
            {
                // 從priorityNodes獲取的是當前優先順序,最後一個節點的元素
                LinkedListNode<Document> prioNode = priorityNodes[priority];
                // 1)直接進來時候,如果優先順序相等,如果是遞迴進來的不會到這裡因為priority變小了
                if (priority == doc.Priority)
                // priority node with the same priority exists
                {
                    // 是當前優先順序直接新增到末尾
                    documentList.AddAfter(prioNode, doc);
                    // set the priority node to the last document with the same priority
                    // 當前優先順序存的是優先順序最後的節點,doc.Priority和prioNode.Next;上的優先順序一樣的
                    priorityNodes[doc.Priority] = prioNode.Next;
                }
                // 2)遞迴時候進來的,因為priority 小於了doc.Priority,且priority有值,所以要放到priority前面
                else // only priority node with a lower priority exists
                {
                    // get the first node of the lower priority
                    LinkedListNode<Document> firstPrioNode = prioNode;

                    while (firstPrioNode.Previous != null &&
                       firstPrioNode.Previous.Value.Priority == prioNode.Value.Priority)
                    {
                        firstPrioNode = prioNode.Previous;
                        prioNode = firstPrioNode;
                    }

                    //沒有放到前面,為了連結串列按照優先順序大在前面
                    documentList.AddBefore(firstPrioNode, doc);
                    // set the priority node to the new value
                    // 當前優先順序存的是優先順序最後的節點,doc.Priority和prioNode.Next;上的優先順序一樣的
                    priorityNodes[doc.Priority] = firstPrioNode.Previous;
                }
            }
        }

        // 按照從大優先順序,相同優先順序先來優先順序高的順序排序
        public void DisplayAllNodes()
        {
            foreach (Document doc in documentList)
            {
                Console.WriteLine("priority: {0}, title {1}", doc.Priority, doc.Title);
            }
        }

        // returns the document with the highest priority
        // (that's first in the linked list)
        // 優先順序高的出連結串列,並且刪除該document
        public Document GetDocument()
        {
            Document doc = documentList.First.Value;
            documentList.RemoveFirst();
            return doc;
        }

    }

}
Program.cs:
namespace Wrox.ProCSharp.Collections
{
    class Program
    {
        static void Main()
        {
            PriorityDocumentManager pdm = new PriorityDocumentManager();
            // 傳入時候就排序好了,LinkList<T>結構方便優先順序型別的插入操作(利於插入和刪除)
            pdm.AddDocument(new Document("one", "Sample", 8));
            pdm.AddDocument(new Document("two", "Sample", 3));
            pdm.AddDocument(new Document("three", "Sample", 4));
            pdm.AddDocument(new Document("four", "Sample", 8));
            pdm.AddDocument(new Document("five", "Sample", 1));
            pdm.AddDocument(new Document("six", "Sample", 9));
            pdm.AddDocument(new Document("seven", "Sample", 1));
            pdm.AddDocument(new Document("eight", "Sample", 1));

            // 展示排序好的
            pdm.DisplayAllNodes();

        }
    }
}

四.Dictionary<TKey,TValue>、多鍵值ILookup<TKey,TValue>、SortedDictionary<TKey,TValue>、HashSet<T>和SortedSet<T>

1.Dictionary<TKey,TValue>

.net提供了幾個字典類,其中最主要的類是Dictionary<TKey,TValue>。
字典基於hash_map儲存結構,提供了快速的查詢方法,查詢效率是O(1),但是也不是絕對的因為要解決hash對映函式計算和解決衝突。
也可以自由的新增和刪除元素,有點像List<T>但是沒有記憶體元素挪動效能開銷。
Dictionary資料結構很類似C++中的hash_map/unordered_map工作方式,或者就是這樣的實現:
hash_map其插入過程是:
    得到key
    通過hash函式得到hash值
    得到桶號(一般都為hash值對桶數求模)
    存放key和value在桶內。

其取值過程是:
    得到key
    通過hash函式得到hash值
    得到桶號(一般都為hash值對桶數求模)
    比較桶的內部元素是否與key相等,若都不相等,則沒有找到。
    取出相等的記錄的value。
因此C#中要用Dictionary類,鍵型別需要重寫:
1)雜湊函式:Object類的GetHashCode()方法,GetHashCode()返回int值用於計算鍵對應位置放置的hashCode用作元素索引。
GetHashCode()實現要求:
相同的鍵總是返回相同的int值,不同的鍵可以返回相同的int值。
它應該執行得比較快,計算開銷不大,hashCode應該儘量平均分佈在int可以儲存的整個數字範圍上。
不能丟擲異常。
至少使用一個鍵物件的欄位,hashCode最好在鍵物件的生存期中不發生變化。

2)解決衝突:鍵型別必須實現IEquatable<T>.Equals()方法,或者重寫Object類的Equals()方法,因為不同的鍵值需要返回不同的hashCode,相同的鍵返回相同hashCode。
預設沒有重寫,那麼Equals方法比較的是引用無論是值型別還是引用型別,GetHashCode()是根據物件的地址計算hashCode,所以預設是基於引用的比較。
相同的int型別傳入,只要不是相同的int引用,就會導致無法返回結果。
所以基礎型別都重寫了上述兩個方法,基礎型別中string比較通過字串值有較好的雜湊平均分佈,int也是通過值比較但是很難平均分佈。

如果重寫了一個Equals方法(一般是值比較),但是沒有重寫GetHashCode()方法(一般也是基於值的獲取hashCode)那麼獲取hashCode的方法將是獲取引用,使用字典類就會導致詭異的行為,將物件放入了字典中,但是取不出來了(因為鍵引用不同),或者取出來的是一個錯誤的結果,所以編譯器會給一個編譯警告!
如果鍵型別沒有重寫GetHashCode()和Equals()方法;也可以實現IEqualityComparer<T>介面的比較器它定義了GetHashCode()和Equals()方法,並將傳遞的物件作為引數,將比較器傳入Dictionary<TKey,TValue>一個過載版本的建構函式即可。
Employee.cs:

using System;
namespace Wrox.ProCSharp.Collections
{
    // 將類的一個例項序列化為一個檔案
    [Serializable]
    public class Employee
    {
        private string name;
        private decimal salary;
        private readonly EmployeeId id;

        public Employee(EmployeeId id, string name, decimal salary)
        {
            this.id = id;
            this.name = name;
            this.salary = salary;
        }

        public override string ToString()
        {
            return String.Format("{0}: {1, -20} {2:C}",
                  id.ToString(), name, salary);
        }
    }
}

EmployeeId.cs:
using System;

namespace Wrox.ProCSharp.Collections
{
    [Serializable]
    public class EmployeeIdException : Exception
    {
        public EmployeeIdException(string message) : base(message)  { }
    }

    [Serializable]
    public struct EmployeeId : IEquatable<EmployeeId>
    {
        private readonly char prefix;
        private readonly int number;

        public EmployeeId(string id)
        {
            if (id == null) throw new ArgumentNullException("id");

            prefix = (id.ToUpper())[0];
            int numLength = id.Length - 1;
            try
            {
                // 擷取前面6位,有可能提供的number一樣,這樣多個鍵會產生一個相同的GetHashCode()。
                // 但是後面會通過Equals方法進行解決衝突。
                number = int.Parse(id.Substring(1, numLength > 6 ? 6 : numLength));
            }
            catch (FormatException)
            {
                throw new EmployeeIdException("Invalid EmployeeId format");
            }
        }

        public override string ToString()
        {
            return prefix.ToString() + string.Format("{0,6:000000}", number);
        }
        // 獲取確定的,int型別上均勻分配的,高效能的產生hashCode方法
        public override int GetHashCode()
        {
            return (number ^ number << 16) * 0x15051505;
        }

        public bool Equals(EmployeeId other)
        {
            if (other == null) return false;
            // number相同情況下,如果prefix也相同,那麼就會導致完全相同了
            return (prefix == other.prefix && number == other.number);
        }

        public override bool Equals(object obj)
        {
            return Equals((EmployeeId)obj);
        }

        public static bool operator ==(EmployeeId left, EmployeeId right)
        {
            return left.Equals(right);
        }

        public static bool operator !=(EmployeeId left, EmployeeId right)
        {
            return !(left == right);
        }
    }
}

Program.cs:
using System;
using System.Collections.Generic;

namespace Wrox.ProCSharp.Collections
{
    class Program
    {
        static void Main()
        {
            // capacity是素數
            var employees = new Dictionary<EmployeeId, Employee>(31);

            var idKyle = new EmployeeId("T3755");
            var kyle = new Employee(idKyle, "Kyle Bush", 5443890.00m);
            employees.Add(idKyle, kyle);
            Console.WriteLine(kyle);

            var idCarl = new EmployeeId("F3547");
            var carl = new Employee(idCarl, "Carl Edwards", 5597120.00m);
            employees.Add(idCarl, carl);
            Console.WriteLine(carl);

            var idJimmie = new EmployeeId("C3386");
            var jimmie = new Employee(idJimmie, "Jimmie Johnson", 5024710.00m);
            var jimmie2 = new Employee(idJimmie, "Jimmie Cen", 5024710.00m);
            employees.Add(idJimmie, jimmie);
            //employees.Add(idJimmie, jimmie2); // 相同key,用Add不會覆蓋,但是會丟擲異常
            Console.WriteLine(jimmie);

            var idDale = new EmployeeId("C3323");
            var dale = new Employee(idDale, "Dale Earnhardt Jr.", 3522740.00m);
            employees[idDale] = dale;
            Console.WriteLine(dale);

            var idJeff = new EmployeeId("C3234");
            var jeff = new Employee(idJeff, "Jeff Burton", 3879540.00m);
            var jeff2 = new Employee(idJeff, "Jeff Cen", 3879540.00m);
            // 下標索引方式新增元素
            employees[idJeff] = jeff;
            employees[idJeff] = jeff2; // 相同key,用下標索引會覆蓋
            Console.WriteLine(jeff);

            while (true)
            {
                Console.Write("Enter employee id (X to exit)> ");
                var userInput = Console.ReadLine();
                userInput = userInput.ToUpper();
                if (userInput == "X") break;

                EmployeeId id;
                try
                {
                    // 第一位字元會去掉,用後面的數字作為真正的key
                    id = new EmployeeId(userInput);

                    Employee employee;
                    // 如果用下標訪問的話,不存在會丟擲異常NotFoundException
                    if (!employees.TryGetValue(id, out employee))
                    {
                        Console.WriteLine("Employee with id {0} does not exist", id);
                    }
                    else
                    {
                        Console.WriteLine(employee);
                    }
                }
                catch (EmployeeIdException ex)
                {
                    Console.WriteLine(ex.Message);
                }
            }
        }
    }
}

2.ILookup<TKey,TValue>

非常類似於Dictionary<TKey,TValue>,會把鍵對映到一個值集上,但是ILookup<TKey,TValue>是在System.Core名稱空間中,用System.Linq名稱空間定義。
ILookup<TKey,TValue>是一個拓展結構,不能像其它容器那樣直接建立,需要從實現了IEnumerable<T>介面的容器中用ToLookup函式獲取。
ToLookup函式需要傳遞一個Func<TSource, TKey>型別的鍵委託,可以用Lambda表示式來實現,例如:

static void Main()
        {
            var racers = new List<Racer>();
            racers.Add(new Racer(26, "Jacques", "Villeneuve", "Canada", 11));
            racers.Add(new Racer(18, "Alan", "Jones", "Australia", 12));
            racers.Add(new Racer(11, "Jackie", "Stewart", "United Kingdom", 27));
            racers.Add(new Racer(15, "James", "Hunt", "United Kingdom", 10));
            racers.Add(new Racer(5, "Jack", "Brabham", "Australia", 14));
           //public static ILookup<TKey, TSource> ToLookup<TSource, TKey>
           //(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector);
           //建立一個1:n 的對映。 它可以方便的將資料分類成組,並生成一個字典供查詢使用。
            //System.Linq.Enumerable::ToLookup
            //public static ILookup<TKey, TSource> ToLookup<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector);
            // this IEnumerable<TSource> source?,是.net的拓展方法機制,用例項呼叫靜態方法,但是編譯器是將例項作為靜態方法的第一個引數來呼叫靜態方法的。
            var lookupRacers = racers.ToLookup(x => x.Country);
            foreach (Racer r in lookupRacers["Australia"])
            {
                Console.WriteLine(r);
            }
        }

3.SortedDictionary<TKey,TValue>

是一個二叉樹,其中元素根據鍵來排序,該鍵型別必須實現IComparable<TKey>介面,或者需要傳遞一個IComparer<TKey>介面的比較器用作有序字典的建構函式的一個引數。其實類似於C++中

的map型別了,類似java中的TreeMap型別。
SortedDictionary<TKey,TValue>和SortedList<TKey,TValue>,但SortedDictionary<TKey,TValue>類插入和刪除元素比較快,查詢速度比較慢,記憶體開銷比SortedList<TKey,TValue>大。

SortedList<TKey,TValue>適用很少修改的情形,因為有更快的查詢速度,用更小的記憶體。

4.HashSet<T>和SortedSet<T>

HashSet<T>是無序的和SortedSet<T>是有序的都實現了介面ISet<T>,ISet<T>提供了集合的交集,並集,判斷集合關係等操作。
直接建立集合物件就可以了,為集合新增元素可以用Add()方法如果重複那麼會返回false不會丟擲異常。
IsSubsetOf和IsSupersetOf方法比較集合實現了IEnumerable<T>介面的集合,並返回一個布林結果,Overlaps()是判斷有交集。
UnionWith()方法將多個集合求並,ExceptWith()求差集。
SortedSet<T>如果是自定義型別,那麼需要提供排序的委託例項。

ObservableCollection<T>類,是為WPF定義的,這樣UI可以得到集合的變化,在名稱空間:System.Collections.ObjectModel中定義。
ObservableCollection<T>派生自Collection<T>基類,所以集合類的很多操作該容器都滿足,並在內部使用了List<T>類。
ObservableCollection<T>物件的CollectionChanged事件可以新增訊息處理函式也就是委託例項,當集合發生變化時候可以回撥到處理函式中。

五.BitArray/BitVector32、IProducerConsumerCollection<T>介面

1.BitArray/BitVector32用集合進行位操作

1).BitArray位於System.Collections名稱空間中,用於不確定位大小的操作,可以包含非常多的位,應該是儲存在堆中。
BitArray是一個引用型別,包含一個int陣列,其中每32位使用一個新整數。
BitArray可以用索引器對陣列中的位進行操作,索引器是bool型別,還可以用Get(),Set方法訪問陣列中的位。
BitArray的位操作,Not非,And()與,Or()或,Xor()異或操作。

2).BitVector32是值型別
BitVector32位於System.Collections.Specialized中,32位的操縱,儲存在棧中,速度很快。
BitVector32屬性方法,Data返回二進位制資料的整型大小.
BitVector32的訪問可以使用索引器,索引器是過載的,可以使用掩碼或BitVector32.Section型別的片段來獲取和設定值。
CreateMask()為結構中的特定位建立掩碼。
CreateSection()用於建立32位中的幾個片段。
位操作例子:

using System;
using System.Collections;
using System.Collections.Specialized;
using System.Text;
namespace BitArraySample
{
    class Program
    {
        static void Main()
        {
            BitArrayDemo();
            BitVectorDemo();
        }

        static void BitArrayDemo()
        {
            var bits1 = new BitArray(8);
            // 全部設定為1
            bits1.SetAll(true);
            // 索引1設定為false
            bits1.Set(1, false);
            // 設定用下標索引器設定值
            bits1[5] = false;
            bits1[7] = false;
            Console.Write("initialized: ");
            DisplayBits(bits1);
            Console.WriteLine();

            // 位的一些運算
            DisplayBits(bits1);
            bits1.Not();
            Console.Write(" not ");
            DisplayBits(bits1);
            Console.WriteLine();

            var bits2 = new BitArray(bits1);
            bits2[0] = true;
            bits2[1] = false;
            bits2[4] = true;
            DisplayBits(bits1);

            Console.Write(" or ");
            DisplayBits(bits2);
            Console.Write(" : ");
            bits1.Or(bits2);
            DisplayBits(bits1);
            Console.WriteLine();

            DisplayBits(bits2);
            Console.Write(" and ");
            DisplayBits(bits1);
            Console.Write(" : ");
            bits2.And(bits1);
            DisplayBits(bits2);
            Console.WriteLine();

            DisplayBits(bits1);
            Console.Write(" xor ");
            DisplayBits(bits2);
            bits1.Xor(bits2);
            Console.Write(" : ");
            DisplayBits(bits1);
            Console.WriteLine();
        }

        static void BitVectorDemo()
        {

            var bits1 = new BitVector32();
            // 書面寫法,不考慮大小端,最後一個位的掩碼(考慮是小端也就是地址讀到的第一個)
            int bit1 = BitVector32.CreateMask();
            // 基於bit1位的上一個位的掩碼,其實bit1是1
            int bit2 = BitVector32.CreateMask(bit1);
            // 基於bit2位的上一個位的掩碼,其實bit2是2
            int bit3 = BitVector32.CreateMask(bit2);
            int bit4 = BitVector32.CreateMask(bit3);
            int bit5 = BitVector32.CreateMask(bit4);

            // 用索引器將末尾取值為1,其實bit1是1
            bits1[bit1] = true;
            // 用索引器將倒數第二位取值為0,其實bit2是2
            bits1[bit2] = false;
            bits1[bit3] = true;
            bits1[bit4] = true;
            Console.WriteLine(bits1);
            // 可以一次性的把有1下標的賦值為true
            bits1[0xabcdef] = true;
            Console.WriteLine(bits1);

            int received = 0x79abcdef;
            // 一次性的把有1下標的賦值為true,為0的賦值位0
            var bits2 = new BitVector32(received);
            Console.WriteLine(bits2);
            // sections: FF EEE DDD CCCC BBBBBBBB AAAAAAAAAAAA
            // 從底地址開始擷取0xfff片段的索引值
            BitVector32.Section sectionA = BitVector32.CreateSection(0xfff);
            // 基於sectionA偏移,取0xff位數上的索引值
            BitVector32.Section sectionB = BitVector32.CreateSection(0xff, sectionA);
            BitVector32.Section sectionC = BitVector32.CreateSection(0xf, sectionB);
            BitVector32.Section sectionD = BitVector32.CreateSection(0x7, sectionC);
            BitVector32.Section sectionE = BitVector32.CreateSection(0x7, sectionD);
            BitVector32.Section sectionF = BitVector32.CreateSection(0x3, sectionE);

            // 用索引片段,訪問bits2在該片段上的元素
            Console.WriteLine("Section A: " + IntToBinaryString(bits2[sectionA], true));
            Console.WriteLine("Section B: " + IntToBinaryString(bits2[sectionB], true));
            Console.WriteLine("Section C: " + IntToBinaryString(bits2[sectionC], true));
            Console.WriteLine("Section D: " + IntToBinaryString(bits2[sectionD], true));
            Console.WriteLine("Section E: " + IntToBinaryString(bits2[sectionE], true));
            Console.WriteLine("Section F: " + IntToBinaryString(bits2[sectionF], true));
        }

        static string IntToBinaryString(int bits, bool removeTrailingZero)
        {
            var sb = new StringBuilder(32);
            for (int i = 0; i < 32; i++)
            {
                // 從左邊讀起,讀完後丟棄左邊位數,所以與上0x80000000
                if ((bits & 0x80000000) != 0)
                {
                    sb.Append("1");
                }
                else
                {
                    sb.Append("0");
                }
                bits = bits << 1;
            }
            string s = sb.ToString();
            if (removeTrailingZero)
                return s.TrimStart('0');
            else
                return s;
        }

        static void DisplayBits(BitArray bits)
        {
            // 可以直接迭代輸出
            foreach (bool bit in bits)
            {
                Console.Write(bit ? 1 : 0);
            }
        }
    }
}

2.IProducerConsumerCollection<T>介面

.net4.0中包含了新的名稱空間System.Collections.Concurrent,定義了一些執行緒安全的集合,這些集合實現了IProducerConsumerCollection<T>介面(生產消費者模式)。
這樣多執行緒訪問這些集合就不需要Lock{}操作了。只需要用相應集合的TryAdd和TryTake方法,這兩個方法分為阻塞的和非阻塞的,如果失敗會返回false否則返回true。
這些集合有:
ConcurrentQueue<T>集合,Enqueue(),TryDequeue()和TryPeek方法。
ConcurrentStack<T>類。
ConcurrentBag<T>類。
ConcurrentDictionary<TKey, TValue>這個是執行緒安全的鍵值集合,TryAdd,TryGetValue, TryRemove,TryUpdate方法以非阻塞方式訪問成員。因為基於鍵和值ConcurrentDictionary<TKey,

TValue>沒有實現IProducerConsumerCollection<T>介面,但是也是支援執行緒安全的。
ConcurrentXXX型別的集合是執行緒安全的。

BlockingCollection<T>定義了集合操作阻塞的介面,使用令牌機制,可以指定等待的最長時間。BlockingCollection<T>是針對實現了IProducerConsumerCollection<T>介面的任意型別修飾器,預設是使用了ConcurrentQueue<T>類。
實現例子:
static void Main()
        {
            BlockingDemoSimple();
        }

        static void BlockingDemoSimple()
        {
            // 阻塞的容器
            var sharedCollection = new BlockingCollection<int>();
            
            // 定義兩個事件物件和等待事件完成的重置訊息控制代碼
            var events = new ManualResetEventSlim[2];
            var waits = new WaitHandle[2];
            for (int i = 0; i < 2; i++)
			{
			    events[i] = new ManualResetEventSlim(false);
                waits[i] = events[i].WaitHandle;
			}

            var producer = new Thread(obj =>
            {
                // 解析傳入的集合容器和事件物件
                var state = (Tuple<BlockingCollection<int>, ManualResetEventSlim>)obj;
                var coll = state.Item1;
                var ev = state.Item2;
                var r = new Random();

                for (int i = 0; i < 300; i++)
                {
                    // 阻塞函式,var coll前面強轉確定型別,可以新增元素
                    coll.Add(r.Next(3000));
                }
                // 事件完成,釋放訊號
                ev.Set();
            });
            producer.Start(Tuple.Create<BlockingCollection<int>, ManualResetEventSlim>(sharedCollection, events[0]));

            var consumer = new Thread(obj =>
            {
                // 解析傳入的集合容器和事件物件
                var state = (Tuple<BlockingCollection<int>, ManualResetEventSlim>)obj;
                var coll = state.Item1;
                var ev = state.Item2;

                for (int i = 0; i < 300; i++)
                {
                    // 阻塞函式,前面強轉確定型別,提取元素
                    int result = coll.Take();
                }
                // 事件完成,釋放訊號
                ev.Set();
            });
            consumer.Start(Tuple.Create<BlockingCollection<int>, ManualResetEventSlim>(sharedCollection, events[1]));

            // 主執行緒會阻塞一直等待訊號到來
            if (!WaitHandle.WaitAll(waits))
                Console.WriteLine("wait failed");
            else
                Console.WriteLine("reading/writing finished");

        }