Generic Collection Classes in C#

Introduction

A C# collection is a group of multiple objects. You can group any type of object in a collection into a single collection using a System.Object class. For that the .Net Framework provides collection classes, some in the System.Collections namespace and some in the System.Collecctions.Generic namespace. As I previously said in my last article, the System.Collection classes are not type safe. But startiing with the .Net Framework 2.0 we were able to use the collections the System.Collections.Generic namespace that is type safe, so here we will focus on the newer collection classes.

There are also some other namespaces from where we can use collection classes. If you want to create a specific type of collection then there are some classes in System.collections.Specialized namespace. If you want to create a thread safe collection then you can use the System.Collections.Concurrent namespace classes. You can also use many other ways to create a collection by implementing interfaces.

C# Lists

For creating a dynamic list, .Net provide us a generic class List<T>. This class implements the IList, ICollection, IEnumrable, IList<T>, ICollection<T> and IEnumerable<T> interfaces.

Interface Description
IList<T> For accessing values from an index. This interface defines an indexer and provides a facility to insert and insert elements from a specific location using InsertAt(), RemoveAt() methods. This interface is derived from ICollection<T>.
ICollection<T> Is implemented by a generic type of collection classes. Using this you can access the number of items in a collection using a Count property. Using Remove (), Add (), Clear () and CopyTo () methods you can remove, add collection elements, and copy to an array.
IEnumerable<T> This interface is required by foreach statements. This interface defines a method GetEnumerator () that returns an Enumerator that implements the IEnumerator interface.

Just look at the scenario where we need to create a list of students where we can store Name, RollNo and Marks for each student. For this I just create a class library with the name Student and this class has three properties Name, RollNo and Marks and using a constructor I'll assign a value for all the properties. This class implements the IComparable<T> interface for sorting the student details.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ListStudent
{
    public class Student : IComparable<Student>, IFormattable
    {
        public int RollNo { get; set; }
        public string Name { get; set; }
        public int Marks { get; set; }
        public Student(string Name, int Marks = 0, int RollNO = 0)
        {
            this.RollNo = RollNO;
            this.Name = Name;
            this.Marks = Marks;
        }
        public string ToString(string format, IFormatProvider formatProvider)
        {
            if (format == null)
            {
                format = "N";
            }
            switch (format.ToUpper())
            {
                case "N":
                    return Name.ToString();
                case "M":
                    return string.Format("{0} Marks: {1} ", Name.ToString(), Marks);
                case "R":
                    return string.Format("{0} RollNO: {1}", Name.ToString(), RollNo);
                case "A":
                    return string.Format("{0} RollNo: {1} Marks: {2}", Name.ToString(), RollNo, Marks);
                default:
                    return string.Format(" Format {0} Is Not Supported !");
            }
        }
        public int CompareTo(Student other)
        {
            return this.Name.CompareTo(other.Name);
        }
    }
}

Now build the above program, after building the program the next step is to create a list. To create a list we can use the List<T> generic collection class where you need to specify a type for the values in the declaration. This class holds three constructors so if you create a list with the default constructor and assign an initial value for the list then the capacity for the list will bec four; if you add a fifth value in the list then the capacity will changed and will allow you to add eight elements. Again if you will add a ninth element then again your list capacity will be doubled again, in ohter words the capacity will become 16. You can create a list object with default constructor using the following:

List<int> list = new List<int>();
List<Student> StudentList = new List<Student>();

Actually what happens is that whenever the capacity is going to be changed it creates a new array with type T and uses Array.Copy () to copy the elements from the old to the new array. This process is a little time consuming; to save time if you know the number of elements then you can specify the capacity of the list calling the second type of constructor as follows:

List<int> list = new List<int>(10);

You can also assign a list capacity after calling the Capacity property as follows:

List<int> list = new List<int>();
list.Capacity = 10;

The capacity is not the same as Count. Count will return the capacity of the list minus the number of inserted values in the list. After inserting a value if you don't need to insert another value then you can call the TrimExcess() method to remove the unneeded capacity. However creating a copy of an array to a new array takes time; if the count is more than of 90% of capacity then it is better do nothing.

list.TrimExcess();

You can also assign a value to a collection using a collection initializer. The syntax of the collection intializer is the same as an array intializer.

List<Student> StudentList = new List<Student>() {
new Student("Jay",80,1),
new Student("Ajay",70,2),
new Student("Sanjay",60,3),
new Student("Digvijay",50,4),
new Student("vijay",40,5)
};

Note: Collection initializers are not reflected within the IL code of the compiled assembly. The compiler converts the collection initializer to invoke the Add () method for every item from the initializer list.

Now for adding elements to a list, we can use the class file above in another program. To do that, add a reference for the DLL of the file above in the other project and include the namespace in you program as follows:

Generic-Collection-Classes-1.jpg

After adding a reference you will be able to use the Student class.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ListStudent;
namespace UseListStudent
{
    class Program
    {
        static void Main(string[] args)
        {
            // adding elements
            List<Student> StudentList = new List<Student>() {
            new Student("Jay",80,1),
            new Student("Ajay",70,2),
            new Student("Sanjay",60,3),
            new Student("Digvijay",50,4),
            new Student("vijay",40,5)
            };
            // accessing element 
            foreach (Student student in StudentList)
            {
                Console.Write(student.Name);
                Console.Write("\t\t" + student.RollNo);
                Console.WriteLine("\t\t" + student.Marks);
            }
        }
    }
}

As I said above, there are three types of constructors and I just explained 2 type of constructors. Now you can also use the third constructor to add an element while creating a list object, this constructor accepts a collection as follows:

List<Student> StudentList1 = new List<Student>(new Student[]{
    new Student("Jay",80,1),
    new Student("Ajay",70,2),
});

You can also insert at any location in the list using the Insert() method, that takes two parameters; the first is an index number and the second value is as follows:

StudentList.Insert(2, new Student("Ajay", 30, 6));

Instead of using a foreach statement you can use the ForEach () method that is declared in the List<T> class that accepts a single parameter declared with the type Action<T>. You will see next how the ForEach() method is declared inside the List<T> class.

class List<T> : IList<T>
{
    private T[] items;
    public void ForEach(Action<T> action)
    {
        if (action == null)
            throw new ArgumentNullException("Message");
        foreach (T item in items)
        {
            action(item);
        }
    }
}

Action<T> is declared as a delegate and defines a method with void return type and a parameter type of T.  So we can pass a method reference for the Action<T> delegate. So since Console.WriteLine () takes a parameter of type object, in such a situation we can pass a reference of Console.WriteLine () as follows:

StudentList.ForEach(Console.WriteLine);
// you can use lambda expression as follows:
StudentList.ForEach(r => Console.WriteLine("{0:A}",r));

For removing elements from a list you can use RemoveAt (), Remove () and RemoveRange() methods. Whereas RemoveAt () accepts a single parameter with int type, the RemoveAt () method with an element index passed to it, can remove any element from the list. Whereas the Remove() method accepts a T type parameter where we can pass an object of the student class. Actually Remove () uses the IndexOf () method to get the index of a specific element and IndexOf () compares all elements using the IEquatable<T> interface. This interface has the method Eqauls () methods which is used to check the equality of elements. If IEquatable<T> is not implemented then in such a condition it uses the Equals() method of the object class and it uses a bitwise compare with value type and compares the reference type with the reference type so it's a time consuming process to determine the index then remove the element so it's batter to use the RemoveAt () method for performance purposes. You can use Remove () and RemoveAt () as follows:

var First = new Student("Jay", 80, 1);
var Sec = new Student("Ajay", 70, 2);
var Third = new Student("Sanjay", 60, 3);
List<Student> StudentList = new List<Student>() { First, Sec, Third };
// use Remove() method
if (StudentList.Remove(Sec))
{
    Console.WriteLine("Value Removed !");
}
else
{
    Console.WriteLine("Not Found !");
}
// use RemoveAt() method
StudentList.RemoveAt(0);

You can use the RemoveRange () method to remove a number of elements that take two int types of parameters. First you need to pass an index and the second parameter is the number of elements as in the following. This will remove the two elements from index zero:

StudentList.RemoveRange(0, 2);

You can search records from a list using IndexOf (), LastIndexOf (), FindIndex (), FindLastIndex (), Find () and FindLast () and if you want to check the existence of an element then you can use the Exist () method. IndexOf () requires an object as a parameter and returns an index if it exists and -1 will be returned if it does not exist. Remember that IndexOf () is using the IEquatable<T> interface to compare the elements.

int Index = StudentList.IndexOf(first);
// if you want to check if you specific element will exist at
// specific index or not it will exist it will return index otherwise -1
int Index = StudentList.IndexOf(sec, 1);
// you can also find specific element with in specific indexes
// if it will be founded in specific index it will return index other wise -1
// following statement will find sec object with in 0-5 index range
int Index = StudentList.IndexOf(sec, 0, 5);

If you want to search for elements that have a specific characteristic then we can use FindIndex (). FindIndex () accepts a Predicate<T> type of parameter as in the following:The Predicate<T> type is a delegate that returns a Boolean value and requires type T as a parameter. The predicate returns true if an element exists and false if not exist.

public delegate bool Predicate<T>(T obj);

As you can see, the above predicate defines a method that returns a type bool value and accepts a single parameter. Since you pass a Student type parameter, the function needs to accept a Student type of object. So for that you can create a class with a name of StudentRollNo where we define a function FindRollNo () that will accept a student type of object in the parameter and returns true as you will see in the following:

using System;

using System.Collections.Generic;
using System.Linq;
using System.Text;
using ListStudent;
namespace UseListStudent
{
    class StudentRollNo
    {
        public StudentRollNo(int RollNo)
        {
            this.RollNo = RollNo;
        }
        private int RollNo
        {
            get;
            set;
        }
        public bool FindRollNo(Student student)
        {
            if (student == null)
                throw new ArgumentNullException("Parameter pass is null !");
            return student.RollNo == RollNo;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            // adding elements
            List<Student> StudentList = new List<Student>() {
            new Student("Jay",80,1),
            new Student("Ajay",70,2),
            new Student("Sanjay",60,3),
            new Student("Digvijay",50,4),
            new Student("vijay",40,5)
            };
            Student first = new Student("Jay", 80, 1);
            var sec = new Student("Ajay", 70, 2);
            // Access index for roll No 2
            int Index = StudentList.FindIndex(new StudentRollNo(2).FindRollNo);
            Console.WriteLine(Index);
        }
    }
}

In the program above you can see I just passed a reference of FindRollNo(). You can also use a lambda expression, as in the following, so that there is no need to create a StudentRollNo class as in the program above.

int Index = StudentList.FindIndex(Roll => Roll.RollNo == 2);

You can find all details for an element using the Find () method that also uses a Predicate<T> type of parameter but returns an object as follows.

Student student = (StudentList.Find(Name => Name.Name == "Digvijay"));
Console.Write("Name: {0}\t  Marks: {1}\t RollNo: {2} \n", student.Name, student.Marks, student.RollNo);

If there are multiple elements with the same characteristics and you want get all the values such as you want all student details with the name Jay then you can use the FindAll () method that also accepts a Predicate<T> type of parameter but after getting the first element it searches for another element with the same characteristics as you will see in the following example:

List<Student> StudentList = new List<Student>() {
new Student("Jay",80,1),
new Student("Ajay",70,2),
new Student("Sanjay",60,3),
new Student("Digvijay",50,4),
new Student("Jay",50,5),
new Student("Jay",60,6),
new Student("Jay",70,7)
};
// Access all element who have name Jay
List<Student> students=(StudentList.FindAll(Name => Name.Name == "Jay"));
foreach (Student student in students)
{
    Console.WriteLine("{0:A}", student);
}

There is the Sort () method to sort the list of elements, where the Sort method are of four types as follows:

public void List<T>.Sort();
public void List<T>.Sort(Comparison<T>);
public void List<T>.Sort(IComparer<T>);
public void List<T>.Sort(int, int, IComparer<T>);

So invoking Sort () without a parameter is possible only if an element in the list implements the IComparable interface. So as in the preceding, I just created a class Library ListStudent.Student and this class also implements the IComparable interface so we can use the Sort () method for sorting our list of records. Sort use the bubble sort method to sort the records. If you want to use a custom type for sorting your records you can do that as follows:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ListStudent;
namespace UseListStudent
{
    public class SortStudent : IComparer<Student>
    {
        public enum Types
        {
            Name,
            RollNo,
            Marks,
        }
        Types type;
        public SortStudent(Types type)
        {
            this.type = type;
        }
        public int Compare(Student x, Student y)
        {
            if (x == null || y == null)
                throw new ArgumentNullException("Message");
            switch (type)
            {
                case Types.Marks:
                    return x.Marks.CompareTo(y.Marks);
                case Types.Name:
                    return x.Name.CompareTo(y.Name);
                case Types.RollNo:
                    return x.RollNo.CompareTo(y.RollNo);
                default:
                    throw new Exception("Invalid Type For compare !");
            }
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            // adding elements
            List<Student> StudentList = new List<Student>() {
            new Student("Jay",80,1),
            new Student("Ajay",70,2),
            new Student("Sanjay",60,3),
            new Student("Digvijay",50,4),
            new Student("Jay",50,5),
            new Student("Jay",60,6),
            new Student("Jay",70,7)
            };
            StudentList.Sort(new SortStudent(SortStudent.Types.RollNo));
            foreach (Student stud in StudentList)
            {
                Console.WriteLine("{0:A}", stud);
            }
        }
    }
}

You can also use another Sort () method that requires a Comparison<T> delegate to sort your list of elements. Comparison<T> accepts two T types of parameters so we can use List<T>.Sort (Comparison<T> ) in following ways:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ListStudent;
namespace UseListStudent
{
    class Program
    {
        static void Main(string[] args)
        {
            // adding elements
            List<Student> StudentList = new List<Student>() {
            new Student("Jay",80,1),
            new Student("Ajay",70,2),
            new Student("Sanjay",60,3),
            new Student("Digvijay",50,4),
            new Student("Jay",50,5),
            new Student("Jay",60,6),
            new Student("Jay",70,7)
            };
            StudentList.Sort((stud1, stud2) => stud1.RollNo.CompareTo(stud2.RollNo));
            foreach (Student stud in StudentList)
            {
                Console.WriteLine("{0:A}", stud);
            }
        }
    }
}

After inserting all elements you can declare your list as read only. For that you can use the AsReadOnly () method that returns a ReadOnlyCollection<T>. ReadOnlyCollection<T> implements the same interfaces that are implemented by List<T>.

Queue

A queue is a collection, where elements are processed FIFO (First In First Out). Elements put in first will be accessed first. A queue can be implemented with the Queue<T> class from System.Collections.Generic. Queue<T> is similar to the List<T> type, Queue<T> also uses a T type of array and also implements the interfaces IEnumerable<T> and ICollection<T> as List<T> but does not implement ICollection<T> because this interface defines Add() and Remove() methods that should not be available for queues. You can't access an element from a list using an index because Queue<T> does not implement IList<T>. The Queue<T> has the two functions Enqueue() and Dequeue (),  the Enqueue () method adds elements to one end and Dequeue () accesses and removes elements from the other end. There are also some other methods and properties such as the Count Property that returns the number of items in the queue. The Peek () method reads an item from the start but does not remove the item whereas Dequeue () reads an item and removes it and if the list is empty then it will throw an InvalidOperationException exception. There is also a method TrimExcess () that can be used to resize the capacity of a queue, Dequeue () removes the elements but does not resize the capacity.

Queue<T> also has a constructor similar to those used with the List<T> type. The default constructor creates an empty queue. As you add items, the capacity will be increased to hold 4,8 or 16 items if the capacity is not defined. If you compare a generic queue and a non-generic queue then the non-generic queue differs from the generic queue because using the default constructor of the non-generic queue creates an array with 32 elements.

To implement a queue using C# we'll create a program to store and manage song information. We will create a class library project with two classes, the first with the name Music and the second with the name ManageLibrary as follows:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace MusicLibraryManagementUsingQueue
{
    public class Music
    {
        public string SongName
        {
            get;
            set;
        }
        public string Song
        {
            get;
            set;
        }
        public Music(string SongName, string Song)
        {
            this.SongName = SongName;
            this.Song = Song;
        }
    }
    public class ManageLibrary
    {
        readonly Queue<Music> queue = new Queue<Music>();
        public void AddSong(Music music)
        {
            lock (this)
            {
                queue.Enqueue(music);
            }
        }
        public Music GetSong()
        {
            Music music = null;
            lock (this)
            {
                music = queue.Dequeue();
            }
            return music;
        }
        public bool IsSongExist
        {
            get
            {
                return queue.Count > 0;
            }
        }
    }
}

In the above program the Music class will store song information for a specified song using the auto-implement properties SongName and Name. After storing a specified song, the object of that music will be stored in the queue using the AddSong () method of the ManageLibrary class and at the same time we will access the song information using the GetSong () method with various threads. Therefore, for calling the AddSong () and GetSong () methods from various threads I will just use the above class library project in another project as follows:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using MusicLibraryManagementUsingQueue;
namespace MusicLibraryManagement
{
    class Management
    {
        ManageLibrary manage;
        public void Start(ManageLibrary manage)
        {
            this.manage = manage;
            new Thread(Run).Start();
        }
        public void Run()
        {
            while (true)
            {
                if (manage.IsSongExist)
                {
                    Music music = manage.GetSong();
                    Console.WriteLine("Song Name: {0}  Song: {1}", music.SongName, music.Song);
                }
                Thread.Sleep(1000);
            }
        }
        static void Main(string[] args)
        {
            Management management = new Management();
            ManageLibrary library = new ManageLibrary();
            management.Start(library);
            for (int j = 9; j > 0; j--)
            {
                Music music = new Music("MusicFile" + j, "Song" + j + ".swf");
                library.AddSong(music);
                Console.WriteLine("{0} song added in queue", music.SongName);
                Thread.Sleep(500);
            }
        }
    }
}

So, here you can see that all songs will be accessed in the first in first out process. And using a for loop inside Main () we just initialize a music object every time and using AddSong() store a music object in the Queue<T> class in the following ways:

Generic-Collection-Classes-2.jpg

Now after running this program you will get the following output:

Generic-Collection-Classes-3.jpg

Stack

A Stack has a tube type of structure, it's processed LIFO (Last In First Out). A stack is similar to a queue. Similar to Queue<T>, the Stack<T> class implements IEnumrable<T> and ICollection interfaces. But a stack has various methods to insert elements at the end and access elements from the beginning.

Methods/Property Description
Count Is a property with the number of elements in the stack
Push () Adds an element on top of the stack
Pop () Removes and returns items from the top; if the list is empty then it will throw an InvalidOperationException exception.
Peek() Returns items from the top and does not remove the item.
Contains () Check if elements exist and return true/false

For implementing a stack I just use a music class from the class library file above and store a music object in the stack as follows:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MusicLibraryManagementUsingQueue;
namespace ImpMusicLibraryUsingStack
{
    class Program
    {
        static void Main(string[] args)
        {
            Stack<Music> stack = new Stack<Music>();
            stack.Push(new Music("Song1", "Song1.swf"));
            stack.Push(new Music("Song2", "Song2.swf"));
            stack.Push(new Music("Song3", "Song3.swf"));
            stack.Push(new Music("Song4", "Song4.swf"));
            foreach (Music music in stack)
            {
                Console.WriteLine("Song Name= {0} \t Song= {1} \n\n", music.SongName, music.Song);
            }
            // Reading elements with Enumrator does not remove elements as you can see after accessing elements for second time its also return element
            foreach (Music music in stack)
            {
                Console.WriteLine("Song Name= {0} \t Song= {1}\n", music.SongName, music.Song);
            }
        }
    }
}

Push () stores a music object in the Stack<T> class as in the following:

Generic-Collection-Classes-4.jpg

After running this program you will get the following output:

Generic-Collection-Classes-5.jpg

Linked List

LinkedList<T> is a doubly-linked list where one element holds the reference of the next and previous elements. The main advantage of using a linked list is that you can store an element at any position in the list. You only need to change the previous and next element reference. But there are some disadvantages of using a linked list such as you can only access records sequentially.

The LinkedList<T> class defines members for accessing the first and last elements from a list and there are some methods to add and remove elements at a specific position in a list, like for adding the elements AddAfter (), AddBefore (), AddFirst (), AddLast () and for removing Remove (), RemoveFirst (), RemoveLast () and for finding elements from the beginning Find () and FindLast () for accessing the last element.

All the methods AddAfter (), AddBefore (), AddFirst () and AddLast () of LinkedList<T> can accept two types of values, the first is a T type and the second is a LinkedListNode<T> resign is Link list must have information about the previous and next element; that's why LinkedList<T> contains an item of type LinkedListNode<T>.

Using the LinkedListNode<T> class you can access the next and previous element in a list. The LinkedListNode<T> class defines a property as a Value, Next, Previous, and List. LinkedList<T> contains elements as a queue.

There is a scenario where we need to store a Music object ordered by an index and for that we will use a LinedList<T> class with a List<LinkedListNode<T>> class, where List<LinkedListNode<T>> will manage the index in order and a music object will be stored in the LinkedList<T> class. For this I just created a class library where we have the three properties Name, Song and Index that are initialized with a constructor as follows:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MusicStore
{
    public class Music
    {
        public string Name
        {
            get;
            set;
        }
        public string Song
        {
            get;
            set;
        }
        public int Index
        {
            get;
            set;
        }
        public Music(string Name, string Song, int Index)
        {
            this.Name = Name;
            this.Song = Song;
            this.Index = Index;
        }
    }
}

After crating the class, create a new project with the name ManageMusic where we have two classes, one with the name MusicManager and the second will be the client. MusicManager will be the heart of our MusicStore Project. From where we will manage the MusicList and we will store an index of the music in a specified order using the AddMusic () and Add () methods as follows:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MusicStore
namespace ManageMusic
{
    class MusicManager
    {
        readonly LinkedList<Music> MusicList;
        readonly List<LinkedListNode<Music>> MusicIndexer;
        public MusicManager()
        {
            MusicList = new LinkedList<Music>();
            MusicIndexer = new List<LinkedListNode<Music>>();
            // Initialize  null in List<LinedListNode<Music>> object MusicIndexer
            for (int i = 0; i < 20; i++)
            {
                MusicIndexer.Add(new LinkedListNode<Music>(null));
            }
        }
        // This function will use be sure Music object have value or not
        // And if Music will not null the Music object will storein MusicList using Add() method
        public void AddMusic(Music music)
        {
            if (music == null)
                throw new ArgumentNullException("Music Object Have null Value");
            Add(music, music.Index);
        }
        public void Add(Music music, int Index)
        {
            // check if index is b/w 0-19 or not?
            if (Index > 19 || Index < 0)
                throw new Exception("Index Must be in b/w 0-19");
            // check if List<LinkedListNode<Music>> have value null or not?
            if (MusicIndexer[Index].Value == null)
            {
                --Index;
                //if List<LinkedListNode<Music>> have value null, check Index >= then 0 or not
                if (Index >= 0)
                {
                    Add(music, Index);
                }
                    // if Index not >= then 0
                else
                {
                    MusicList.AddLast(music);
                    MusicIndexer[music.Index] = MusicList.Last;
                }
            }
            // if  List<LinkedListNode<Music>> have value is != to null
            else
            {
                LinkedListNode<Music> IndexerNode = MusicIndexer[Index];
                // if index is = to index value of current music object
                if (Index == music.Index)
                {
                    MusicList.AddAfter(IndexerNode, music);
                    MusicIndexer[music.Index] = IndexerNode.Next;
                }
                // if index is != to index value of current music object
                else
                {
                    // get first node that have lowest index
                    LinkedListNode<Music> FirstIndexerNode = IndexerNode;
                    // Run loop till previous of first node that have lowest index LinkedListNode<Music> != null
                    //and also must have to check index of first node that have lowest index is = to
                    while (FirstIndexerNode.Previous != null && FirstIndexerNode.Previous.Value.Index == IndexerNode.Value.Index)
                    {
                        FirstIndexerNode = IndexerNode.Previous;
                        IndexerNode = FirstIndexerNode;
                    }
                    MusicList.AddBefore(FirstIndexerNode, music);
                    MusicIndexer[music.Index] = FirstIndexerNode.Previous;
                }
            }
        }
        public void DisplayAll()
        {
            Console.WriteLine("_____________________________________________________\n");
            Console.WriteLine("Name \t\t Song \t\t\t Index");
            Console.WriteLine("_____________________________________________________\n\n");
            foreach (Music music in MusicList)
            {
                Console.WriteLine("{0} \t\t {1} \t\t {2} ", music.Name, music.Song, music.Index);
            }
            Console.WriteLine("_____________________________________________________\n");
        }
        public Music GetMusic()
        {
            Music music = MusicList.First.Value;
            MusicList.RemoveFirst();
            return music;
        }
    }
    class client
    {
        static void Main()
        {
            MusicManager manage = new MusicManager();
            manage.AddMusic(new Music("Song1", "Song1.mp3", 7));
            manage.AddMusic(new Music("Song2", "Song2.mp3", 1));
            manage.AddMusic(new Music("Song3", "Song3.mp3", 5));
            manage.AddMusic(new Music("Song4", "Song4.mp3", 3));
            manage.AddMusic(new Music("Song5", "Song5.mp3", 6));
            manage.AddMusic(new Music("Song6", "Song6.mp3", 7));
            manage.AddMusic(new Music("Song7", "Song7.mp3", 2));
            manage.AddMusic(new Music("Song8", "Song8.mp3", 3));
            manage.AddMusic(new Music("Song9", "Song9mp3", 6));
            manage.AddMusic(new Music("Song10", "Song10.mp3", 9));
            manage.AddMusic(new Music("Song11", "Song11.mp3", 0));
            manage.AddMusic(new Music("Song12", "Song12.mp3", 5));
            manage.DisplayAll();
        }
    }
}

After runing this program you will get the following output:

Generic-Collection-Classes-6.jpg

SortedList

This class sorts the data by a key. You can use this class when a collection must be sorted by a key. You can specify a separate type for the values and the keys so if you want a key as a string or any object type then you can specify the type of your key.

SortedList<TKey, TValue> has three constructors. You can use a default constructor, which is used to create an empty list. The second constructor holds an int type of a single parameter for defining the capacity of the list and the third and last constructor takes an object that implements the IComparer<TKey> interface.

To add elements to a SortedList<TKey, TValue> you can use the Add () method with the two parameters TKey and TValue. Instead of using the Add () method you can also use an index value to store elements. Storing an element with the same key using the Add () method will thow an ArgumentException whereas using an index value if you store an element with the same element then it will replace the older value.

While accessing elements you can use a foreach statement, where the value returned by the enumerator is of type KeyValuePair<TKey, TValue> that contains both the key and the value. So you can use the Key property to access the key and use the Value property to access the value of an element as will see in the following example where I just use a key as a string type and the value as a Music object type. So for using the Music object I just create a class library with the name MusicStore and in that class library a Music class exists as follows:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MusicStore
{
    public class Music
    {
        public string SongName
        {
            get;
            set;
        }
        public string Song
        {
            get;
            set;
        }
        public Music(string SongName, string Song)
        {
            this.SongName = SongName;
            this.Song = Song;
        }
    }
}

Now after building the preceding class library you can use it in the following class after adding reference as follows:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MusicStore;
namespace SortedList
{
    class Program
    {
        SortedList<string, Music> sortedList = new SortedList<string, Music>();
        public void AddMusic()
        {
            sortedList.Add("6Key", new Music("Song6", "Song6.swf"));
            sortedList["1Key"] = new Music("Song1", "Song1.swf");
            sortedList["3Key"] = new Music("Song3", "Song3.swf");
            sortedList["2Key"] = new Music("Song2", "Song2.swf");
        }
        public void GetMusic()
        {
            foreach (KeyValuePair<string, Music> obj in sortedList)
            {
                // use Key and Value Property of KeyValuePair<TKey, TValue> class
                Console.WriteLine(obj.Key + "\t" + obj.Value);
            }
        }
        static void Main(string[] args)
        {
            Program p = new Program();
            p.AddMusic();
            p.GetMusic();
        }
    }
}

After running this program you will get the following output where you can see its returned elements in sorted format:

Generic-Collection-Classes-7.jpg

You can also access an element using an index value but while passing an index value that does not exist it throws an exception KeyNotFoundException. Therefore for avoiding this exception you can use a ContainsKey () method that returns true or false depending on whether the key exists.

public void GetValueForKey(string Key)
{
    if (sortedList.ContainsKey(Key))
    {
        Console.WriteLine("Song Name: " + sortedList[Key].SongName + " \tSong :" + sortedList[Key].Song);
    }
}

Or there is the second option, to use TryGetValue() that returns a value using an out parameter if the key exists but does not throw an exception if the value does not exist as you will see in the following:

public void GetValueForKey(string Key)
{
    Music Value;
    if (sortedList.TryGetValue(Key, out Value))
    {
        Console.Write(Value.Song + " " + Value.SongName);
    }
}

Just a minute

Using SortedList<TKey, TValue> you can store only a single value per key but if you want to store multiple values per key then you can use the Lookup<TKey, TElement> class.

End of this session

In this session we just covered various types of generic collection classes. Arrays are fixed in size but you can use lists for dynamically growing a collection. But since collection classes use objects to store values there is the disadvantage of type safety; that's why Microsoft recommends you to use generic collection classes to store dynamic values. For accessing elements on the basis of first-in first-out you can use a queue and a stack for the purposes of last-in first-out. You can use a linked list for storing and removing elements but it's slow if you are searching for records.

What you will get in the next part

Since linked lists provide poor performance for searching records in the list, we can use a dictionary that will be explained in the next session and then you can learn about LookUp, observable collections and concurrent collection classes that will be totally thread-safe class.


Similar Articles