Collections

.Silverlight Collections
List<T> | Dictionary<T> | ObservableCollection<T> | BlockingCollection<T>

"A collection is a set of similarly typed objects that are grouped together in a data structure. The most commonly used collections include lists, dictionaries, stacks, queue, and hash tables. Specialized collections are used to support performance (HybridDictionary), Boolean values (BitVector32), concurrency (ConcurrentQueue), and event-driven programming (ObservableCollection). Most .NET collections support the use of the foreach statement to iterate through the collection."

Collections were designed to provide a simple way of creating commonly used data structures (lists, stacks, queues, etc). Collections contain the ability to automatically resize themselves as needed. Collections also included methods for manipulating the data in the structure (add, put, enqueue, etc) and properties containing information about the structure (such as Count). Additionally, most .NET collections support the use of the foreach statement to iterate through the collection. MSDN contains an article called Selecting a Collection Class which provides guidance on selecting the correct type of collection class to use in your program. Additionally, see my Generics article for information about using generics with collections.

Collections are grouped by functionality in the following namespaces:

  • System.Collections - contain deprecated non-generic collections for versions of .NET prior to 2.0.

  • System.Collections.Generic - contains interfaces and classes that define generic collections, which allow users to create strongly typed collections that provide better type safety and performance than non-generic strongly typed collections.

  • System.Collections.Specialized - contains specialized and strongly-typed collections; for example, a linked list dictionary, a bit vector, and collections that contain only strings.

  • System.Collections.Concurrent - provides thread-safe collection classes that should be used in place of the corresponding types in the System.Collections.Generic namespaces whenever multiple threads are accessing the collection concurrently.

  • System.Collections.ObjectModel - contains classes that can be used as collections in the object model of a reusable library. Use these classes when properties or methods return collections.



System.Collections

"The collections in the System.Collections namespace are no longer recommended. They are included in the .NET base class library for code which targets .NET versions prior to 2.0."

The System.Collections namespace contain the non-generic collection classes, which are no longer recommended. When generics was added to NET 2.0, the collection classes were all refactored to use generics. The generic collections have better performance than the non-generic collections. Generic collections also have better type-safety. See the Generics article for more information about the history of .NET collections and the benefits of using generics to create collections.



System.Collections.Generic

Generic collections are recommended over the non-generic collections for code that targets .NET 2.0 and more recent versions. Generic collections are stored in the System.Collections.Generic namespace. Some of the commonly used generic collection from that namespace are:

  • Dictionary<TKey, TValue> - provides a mapping from a set of keys to a set of values. Each addition to the dictionary consists of a value and its associated key. Retrieving a value by using its key is very fast because the Dictionary<TKey, TValue> class is implemented as a hash table.

  • LinkedList<T> - is a doubly linked list, where each node points forward to the Next node and backward to the Previous node.

  • List<T> - a list of data elements that can be accessed by index. Provides methods to search, sort, and manipulate lists.

  • Queue<T> - a first-in, first-out collection of objects. Data elements are inserted at one end and removed from the other.

  • SortedDictionary<TKey, TValue> - collection of key/value pairs that are sorted on the key. The underlying structure is a binary search tree.

  • SortedSet<T> - collection of data elements that is maintained in sorted order. Duplicate elements are not allowed. Changing the sort values of existing items is not supported and may lead to unexpected behavior.

  • Stack<T> - is a last-in-first-out (LIFO) collection. Accepts null as a valid value for reference types and allows duplicate elements.

List<T>

.List Generic Collection


List<T> - Program Output

"Represents a strongly typed list of objects that can be accessed by index. Provides methods to search, sort, and manipulate lists."

using System;
using System.Collections.Generic;

namespace GenericListExample
{
    class Program
    {
        private static bool RemovePredicate(String s)
        {
            return s.ToLower().EndsWith("motor");
        }

        static void Main()
        {
            List<string> myList = new List<string>()
            {
                "Honda",
                "Suzuki",
                "Beta Motor",
                "Triumph",
                "Norton",
                "Harley",
                "Yingang Motor"
            };

            System.Console.WriteLine(" -----------------------------------");
            System.Console.WriteLine(" ----    List<T> Collection     ----");
            System.Console.WriteLine(" -----------------------------------\n");

            // Sort (using default comparer) and print in ascending order
            System.Console.WriteLine(" ---- Original: Ascending Order ----");
            myList.Sort();
            foreach (string str in myList)
                System.Console.WriteLine(str);
            System.Console.WriteLine();

            // Sort (using lambda expression) and print in descending order
            System.Console.WriteLine(" ---- Original: Descending Order ---");
            myList.Sort((s1, s2) => s2.CompareTo(s1));
            foreach (string str in myList)
                System.Console.WriteLine(str);
            System.Console.WriteLine();

            // Modify list and print in ascending order
            myList.Add("Indian");
            myList.Remove("Suzuki");           
            string[] germanBikes =
            {
                "BMW",
                "Sachs",
                "Zundapp"
            };
            myList.AddRange(germanBikes); // Add Array
            myList.RemoveAll(RemovePredicate);
            System.Console.WriteLine(" ---- Modified: Ascending Order ----");
            myList.Sort();
            foreach (string str in myList)
                System.Console.WriteLine(str);
            System.Console.WriteLine();

            // Print list properties
            System.Console.WriteLine(" --- List Properties ---");
            System.Console.WriteLine("Count is: {0}", myList.Count);
            System.Console.WriteLine("Capacity is: {0}",myList.Capacity);
            myList.TrimExcess();
            System.Console.WriteLine("Trimmed Capacity is: {0}", myList.Capacity);
           
            // Find all the list entries containing "t" or "T" using lambda predicate
            System.Console.WriteLine("\n --- Search Results ---");
            List<string> searchResults = myList.FindAll(s => s.IndexOf("t", StringComparison.OrdinalIgnoreCase) >= 0);          
            foreach (string str in searchResults)
                System.Console.WriteLine(str);
            Console.WriteLine();
        }
    }
}

Top

Dictionary<TKey, TValue>

.Dictionary Collection


Dictionary<TKey, TValue> - Program Output

"A Dictionary allows for fast data lookup by assigning a key value to the data (key/value pairs). A dictionary can contain duplicate values, but not duplicate keys. A dictionary is not sorted, nor is the insert order maintained. The underlying data structure used for a Dictionary is a hash table."

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

namespace GenericDictionaryExample
{
    class Program
    {
        private static bool RemovePredicate(String s)
        {
            return s.ToLower().EndsWith("motor");
        }

        static void Main()
        {
            Dictionary<int,string> myDictionary = new Dictionary<int,string>()
            {
                {1, "Honda"},
                {2, "Suzuki"},
                {3, "Beta Motor"},
                {4, "Triumph"},
                {5, "Norton"},
                {6, "Harley"},
                {7, "Yingang Motor"}
            };

            System.Console.WriteLine(" ---------------------------------------------");
            System.Console.WriteLine(" ---- Dictionary<TKey, TValue> Collection ----");
            System.Console.WriteLine(" ---------------------------------------------\n");

            // Print Dictionary entries
            System.Console.WriteLine(" ---- Original: Dictionary Entries ----");
            foreach (KeyValuePair<int,string> kvp in myDictionary)
                System.Console.WriteLine("Key: {0} Value: {1}", kvp.Key, kvp.Value);
            System.Console.WriteLine();

            // Try to add key that already exists
            int myKey = 1;
            try
               { myDictionary.Add(myKey, "Indian"); }
            catch (Exception e)
               { Console.WriteLine("! Dictionary exception, key: {0} \n! Message: {1}\n", myKey, e.Message); }

            // Modify Dictionary and print entries
            myDictionary.Add(8, "Indian");
            myDictionary.Add(9, "Norton");
            myDictionary.Remove(1);
            foreach (var item in myDictionary)
                System.Console.WriteLine("Key: {0} Value: {1}", item.Key, item.Value);
            System.Console.WriteLine();

            // Key Lookup: Two ways to look up by key
            int lookupKey = 4;
            string foundValue = default(string);

            if (myDictionary.ContainsKey(lookupKey)) // 1. ContainsKey()
                Console.WriteLine("Found using ContainsKey(). Key: {0} Value: {1}", lookupKey, myDictionary[lookupKey]);
           
            if (myDictionary.TryGetValue(lookupKey, out foundValue)) // 2. TryGetValue()
                Console.WriteLine("Found using TryGetValue(). Key: {0} Value: {1}", lookupKey, foundValue);

            // Value Lookup: Get keys for entries that match value (Uses Linq)
            string lookupValue = "Norton";
            var keysMatchingValues = myDictionary.Where(p => p.Value == lookupValue).Select(p => p.Key);

            foreach (var key in keysMatchingValues)
                Console.WriteLine("Found Value: {0} with Key: {1}", myDictionary[key] ,key);
            Console.WriteLine();

            // Put the dictionary values into a list
            Dictionary<int, string>.ValueCollection myValues = myDictionary.Values;
            List<string> myList = new List<string>();

            foreach (string s in myValues)
                myList.Add(s);
            myList.Sort();

            Console.WriteLine("List contains following {0} items:", myList.Count);
            foreach (string s in myList)
                Console.WriteLine(s);
            Console.WriteLine();
        }
    }
}

Top

ObservableCollection<T>

.Observable Collection


ObservableCollection<T> - Program Output

"An ObservableCollection fires the CollectionChanged event whenever an item is inserted, removed, relocated, or modified. The Action property indicates the type of the change and can be used with the OldItems and NewItems properties for reporting change details."

using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;

namespace ObservableCollectionExample
{
    class Program
    {
        static int changeNumber = 0;

        class Character
        {
            public string Name { get; set; }
            public string VoiceActor { get; set; }
            public string Film { get; set; }

            // ToString override
            public override string ToString()
            {
                return string.Format("Name: {0}\n      Voice Actor: {1} \n      Film: {2}",
                                    Name, VoiceActor, Film);
            }
        }

        static void Main()
        {

            System.Console.WriteLine(" ------------------------------- ");
            System.Console.WriteLine(" --- ObservableCollection<T> --- ");
            System.Console.WriteLine(" -------------------------------\n ");

            ObservableCollection<Character> disneyCharacters = new ObservableCollection<Character>()
            {
                new Character {Name = "Bernard",
                                VoiceActor = "Bob Newheart",
                                Film = "The Rescuers"},
                new Character {Name = "Cogsworth",
                                VoiceActor = "David Ogden Stiers",
                                Film = "Beauty and the Beast"},
                new Character {Name = "Dixie",
                                VoiceActor = "Reba McEntire",
                                Film = "The Fox and the Hound 2"},
                new Character {Name = "Figaro the Cat",
                                VoiceActor = "Mel Blanc",
                                Film = "Pinocchio"},
                new Character {Name = "Jessica Rabbit",
                                VoiceActor = "Kathleen Turner",
                                Film = "Who Framed Roger Rabbit"}
            };

            // Print original Observable Collection
            System.Console.WriteLine(" ---   Orignal Collection    --- ");
            foreach (Character theCharacter in disneyCharacters)
                System.Console.WriteLine(theCharacter.ToString());

            // Hook up CollectionChanged event
            disneyCharacters.CollectionChanged += DisneyCollectionChanged;

            // Change Collection
            System.Console.WriteLine("\n --- Collection Modifications -- ");
            disneyCharacters.Add(new Character { Name = "Merlock",
                                                    VoiceActor = "Christoper Lloyd",
                                                    Film = "Duck Tales" });
            disneyCharacters.RemoveAt(3);
            foreach (Character theCharacter in disneyCharacters)
            {
                if (theCharacter.Name == "Bernard")
                {
                    disneyCharacters.Remove(theCharacter);
                    break;
                }
            }

            // Print modified Observable Collection
            System.Console.WriteLine("\n ---   Modified Collection   --- ");
            foreach (Character theCharacter in disneyCharacters)
                System.Console.WriteLine(theCharacter.ToString());
            System.Console.WriteLine();

        }

        static void DisneyCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            System.Console.WriteLine("{0}. Collection Action: {1}", ++changeNumber, e.Action);
            System.Console.WriteLine("   Change Timestamp : {0}", DateTime.Now.ToString());
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    System.Console.WriteLine("      {0}\n", e.NewItems[0]);
                    break;

                case NotifyCollectionChangedAction.Remove:
                    System.Console.WriteLine("      {0}\n", e.OldItems[0]);
                    break;
            }
        }
    }
}

Top

BlockingCollection<T> to produce Producer Consumer

.BlockingCollection for Producer Consumer


BlockingCollection<T> - Program Output

"Blocking Collection provides blocking and bounding capabilities for thread-safe collections that implement IProducerConsumerCollection<T>."

IProducerConsumerCollection<T> represents a collection that allows for thread-safe adding and removal of data. BlockingCollection<T> is used as a wrapper for an IProducerConsumerCollection<T> instance, and allows removal attempts from the collection to block until data is available to be removed. Similarly, you can create a BlockingCollection<T> to enforce an upper bound on the number of data elements allowed in the IProducerConsumerCollection<T>; addition attempts to the collection may then block until space is available to store the added items. In this manner, BlockingCollection<T> is similar to a traditional blocking queue data structure, except that the underlying data storage mechanism is abstracted away as an IProducerConsumerCollection<T>.

BlockingCollection for Producer Consumer

namespace BlockingCollectionExample
{
    using System;
    using System.Collections.Concurrent;
    using System.Threading;
    using System.Threading.Tasks;

    class Program
    {
        static void Main()
        {
            const int itemsToAdd = 500;
            const int maxItems = 100;

            // Preserve all the display output for Adds and Takes
            Console.SetBufferSize(80, (itemsToAdd * 2) + 3);

            BlockingCollection<int> numbers = new BlockingCollection<int>(maxItems);

            // A simple blocking consumer with no cancellation.
            Task.Run(() =>
            {
                int i = -1;
                while (!numbers.IsCompleted)
                {
                    try
                    {
                        i = numbers.Take();
                    }
                    catch (InvalidOperationException)
                    {
                        Console.WriteLine("Adding was compeleted!");
                        break;
                    }
                    Console.WriteLine("Take:{0} Count={1}", i, numbers.Count);

                    Thread.SpinWait(100000);
                }

                Console.WriteLine("\nNo more items to take.");
            });

            // A simple blocking producer with no cancellation.
            Task.Run(() =>
            {
                for (int i = 0; i < itemsToAdd; i++)
                {
                    numbers.Add(i);
                    Console.WriteLine("Add:{0}  Count={1}", i, numbers.Count);
                }

                numbers.CompleteAdding();
            });

            Console.ReadLine();
        }
    }
}

Top


Reference Articles



Top