Collections In C#

Introduction

This article aims to give a quick overview of Collections in C#. Also, we will be demonstrating the traditional method of storing, managing, and manipulating data, pointing out the problems associated with it, and how the Collections can be utilized to help us achieve those goals.

Storing and Manipulating data with traditional methods

An Array is the data structure that stores a fixed number of values (elements), Array is a collection of similar types of values that are stored in sequential order. Elements in an array are stored in a contiguous memory location.

The Array class provides the following methods,

  • Sort: Sorting elements of Array.
  • Reverse: Reversing the elements of Array.
  • Copy: Copying the elements from one Array to another.
  • Length: Returns the total no. of elements from Array.
  • Resize: Creates a new array with a new size and copies the previous array's elements to the new array.

And many more such Methods. Let's look at some of the Methods in the following illustration,

using System;
class Arrays {
    public void arrayExample {
        //Declaring an Array
        int[] array = new int[8];
        //Adding value to array
        array[0] = 55;
        array[1] = 78;
        array[2] = 99;
        array[4] = 13;
        array[5] = 9;
        //Length of the Array
        Console.WriteLine("Length of the Array: " + array.Length);
        //printing the elements of Array
        Console.WriteLine("Elements in Array are: ");
        foreach(int elementsArray in array) {
            Console.Write(elementsArray + " ");
        }
        //Sorting of array elements
        Array.Sort(array);
        //Printing Sorted Elements
        Console.WriteLine("\nElements After Sorting: ");
        foreach(int sortElements in array) {
            Console.Write(sortElements + " ");
        }
        //Reversing the Elements
        Array.Reverse(array);
        //Printing Reversed Array
        Console.WriteLine("\nReversed Array");
        foreach(int reversedArray in array) {
            Console.Write(reversedArray + " ");
        }
        //Copying Array elements to other Array
        int[] copyArray = new int[10];
        //Copy will Take Source Array Destination Array and no of elements we wanna copy
        Array.Copy(array, copyArray, 6);
        //Printing elements of array copyArray
        Console.WriteLine("Elements in New Array");
        foreach(int copyArrayElements in copyArray) {
            Console.Write(copyArrayElements + " ");
        }
        //Resizing Array
        Array.Resize(ref array, 10);
        //New Size of the Array
        Console.WriteLine("\nAfter Resizing Length of the Array:" + array.Length);
    }
}

Above Program Results the following Output:

Length of the Array: 8
Elements in Array are:
55 78 99 0 13 9 0 0
Elements After Sorting:
0 0 0 9 13 55 78 99
Reversed Array
99 78 55 13 9 0 0 0 Elements in New Array
99 78 55 13 9 0 0 0 0 0
After Resizing Length of the Array:10

Hence, we can conclude, from the above illustration, that arrays have the following advantages and disadvantages:

Advantages

  1. Arrays are Strongly Typed, which means we can store a similar type of data using the same name.
  2. As they are Strongly-Typed the performance of the Application will be much faster because there will be no need for Implicit and Explicit Conversion.
  3. A type mismatch will prevent runtime errors. During compile time, you will get an error if there is a type mismatch.
  4. Elements in an array are stored in a contiguous memory location so it becomes easy to just retrieve using the index of the Array

Disadvantages

  1. The array is of Fixed Length.
  2. Size is Assigned during the time of declaring the array so, we should know in advance how many elements are going to be stored in the array.
  3. As we can never increment the size of an array, we can do this manually by creating a new array and copying the old array’s elements into the new array.
  4. We can use the Resize method but it also works the same as stated i.e first creates a new array and then copy the elements from the old array to the new array.
  5. In Array, we can’t insert an element in the middle of an array. It is also not possible to delete or remove elements from the middle of an array.

To overcome all the problems occurring in Array we use a different type of Collections.

Introduction to Collections in C#

In simple terms, Collections are Classes defined to Store, Manage, and Manipulate similar data more precisely and in a more flexible way for grouping objects. As we noticed that we have to define the size of the array beforehand, at the time of declaring the array. In Collections, one does not need to define the collection's size because its size can grow or shrink as needed. we can also add or remove elements from any part of the collection at any time.

All the Collections Classes are present inside the System.Collections Namespace.

Furthermore Collections are classified into two types:

  • Non-Generic type
  • Generic type

Lets’s Discuss both of this one by one:

Non-Generic Collections

Non-Generic Collections Stores the elements as Object types so that they can store any type of data. Also, they are not Strongly Typed. We won't get any compile time error in using any type of Data.

Some of the Non-Generic Collections,

  1. ArrayList: ArrayList stores Objects of any type of data, also size of ArrayList is dynamically adaptable.
  2. SortedList: It stores the key and value pairs and also automatically arranges elements in ascending order by keys as a default reference.
  3. Stack: It stores the values in LIFO (Last In First Out) style. It provides Push(), Pop() and Peek() Method to work with elements.
  4. Queue: It stores the value in FIFO (First In First Out) style. It provides Enqueue() and Dequeue() Methods to Add and Retrieve values.
  5. Hashtable: Hashtable stores the Key and Value Pair but during retrieving it retrieves by comparing the hash value of the Key.

Let's take a look at each of them one by one:

C# Non-Generic ArrayList

ArrayList is pretty much similar to Array except for the dynamically adaptable size feature in ArrayList, Also It is used to add the Unknown data where we have no idea about the type of data that is needed to insert.

We can also add or delete elements from the middle of the ArrayList.

Let's take a look at an example,

using System;
using System.Collections;
class Array_List {
    public void arrayListExample() {
        //Creating Array List
        ArrayList arlist = new ArrayList();
        //Length of Array List
        Console.WriteLine("Length of Array List is: " + arlist.Capacity);
        //Adding Values in Array List
        arlist.Add(1); //Int Value
        arlist.Add("Hello C# Developers"); //String Value
        arlist.Add(true); //Boolean Value
        arlist.Add(DateTime.Now); //DateTime
        arlist.Add(1.2); //Double value
        Console.WriteLine("Data Added Successfully!!");
        //Again Check for Length of ArrayList
        Console.WriteLine("After Adding the Data Length of ArrayList is: " + arlist.Capacity);
        //Printing elements from ArrayList
        Console.WriteLine("Elements In ArrayList are: ");
        foreach(object values in arlist) {
            Console.WriteLine(values);
        }
        //Adding Element in Middle of the Array List
        arlist.Insert(2, "Inserted In Between");
        //Printing Result
        Console.WriteLine("Elements After Inserting value in Middle:");
        foreach(object newValues in arlist) {
            Console.WriteLine(newValues);
        }
        //Removing Element from the Middle of the Array List
        arlist.RemoveAt(4);
        //Printing Result
        Console.WriteLine("Elements After Removing one Element from Middle: ");
        foreach(object updatedValues in arlist) {
            Console.WriteLine(updatedValues);
        }
    }
}

Program returns the following output:

Length of Array List is: 0
Data Added Successfully!!
After Adding the Data Length of ArrayList is: 8
Elements In ArrayList are:
1
Hello C# Developers
True
04/04/2022 09:55:25
1.2
Elements After Inserting value in Middle:
1
Hello C# Developers
Inserted In Between
True
04/04/2022 09:55:25
1.2
Elements After Removing one Element from Middle:
1
Hello C# Developers
Inserted In Between
True
1.2

So, here we can conclude that it consists of variable Length and can insert and delete an element at any given index of the collection.

C# Non-Generic SortedList:

SortedList stores the data in key and Value Pairs. We can access the element by either using key or using index of the element. As this SortedList is Non-Generic type we can store the values of same data type or with different data types.

C# Non-generic SortedList is defined under System.Collections namespace.

Let's have a look at an example:

using System;
using System.Collections;
class SortedListEx {
    public void SortedListExample() {
        //Creating new Sorted List
        SortedList sortedList = new SortedList();
        //Adding Key Value Pairs to SortedList
        sortedList.Add(4, "This is Fourth");
        sortedList.Add(3, "This is Third");
        sortedList.Add(2, 2);
        sortedList.Add(1, DateTime.Now);
        //Printing Values
        foreach(object values in sortedList.Values) {
            Console.WriteLine(values);
        }
    }
}

It results in the following output:

04/04/2022 12:20:28
2
This is Third
This is Fourth

So, from the above example we can conclude that the key that is used in the SortedList is of type Object.

Note: We cannot store keys of different data types in the same SortedList because the compiler will throw an exception. Key should be of same data type throughout the SortedList as the Sorting of the Collection is done in Context of Key.

C# Non-Generic Stack

A Stack represents LIFO (Last In First Out) Collections of the Objects. It is a dynamic Collection that grows by adding elements to it. We can Perform Push() and Pop() for adding and removing the elements from the Stack. As it is Non Generic type Stack so, It can consist of elements with same or with different data types. 

C# Non-Generic Stack is defined under System.Collections namespace.

Let's have a look at an example:

using System;
using System.Collections;
class StackEx {
    public void StackExample() {
        //Creating Stack
        Stack stack = new Stack();
        //Pushing Values into it
        stack.Push(1);
        stack.Push("Second");
        stack.Push(3.0);
        stack.Push(true);
        stack.Push(null);
        stack.Push(DateTime.Now);
        stack.Push("This is to be Popped");
        //Popping Out Value
        stack.Pop();
        //Printing Out Values inside Stack
        Console.WriteLine("Values Inside the Stacke Are:");
        foreach(object values in stack) {
            Console.WriteLine(values);
        }
        //Values at top of the Stack
        Console.WriteLine("Values at Top of the Stack is:" + stack.Pop());
    }
}

Above program results in the following output:

Values Inside the Stacke Are:
04/04/2022 13:08:48
True
3
Second
1
Values at Top of the Stack is:04/04/2022 13:08:48

So, from the above program we can conclude that the Values that are Retrieved from the Stack are in LIFO Manner. We can also include null value in the stack.

C# Non-Generic Queue

Queue is used in FIFO (First In First Out) Collections of Object. Generally, if we need to retrieve the values in a manner in which they are stored then we use Queue. Enqueue() and Dequeue() methods are used to add and remove the values from the Queue.

C# Non Generic Queue is defined under System.Collections namespace.

Let's have a look at an example:

using System;
using System.Collections;
class QueueEx {
    public void QueueExample() {
        //Creating Queue
        Queue queue = new Queue();
        //Adding Element to Queue
        queue.Enqueue("Element to be Delete");
        queue.Enqueue(1);
        queue.Enqueue("Second");
        queue.Enqueue(3.0);
        queue.Enqueue(null);
        queue.Enqueue(true);
        queue.Enqueue(DateTime.Now);
        //Removing First Element
        queue.Dequeue();
        //Printing Elements from the Queue
        Console.WriteLine("Elements in the Queue are");
        foreach(object values in queue) {
            Console.WriteLine(values);
        }
    }
}

Above program results in the following output:

Elements in the Queue are
1
Second
3
True
04/04/2022 13:30:15

So, from the above example we can conclude that in Queue during retrieving we gets the output in an order in which it was inserted.

C# Non-Generic Hashtable

Hashtable is a Collection of the Key Value Pairs which are arranged on the basis of the hash code of the key. During accessing the value hashcode of the key is mapped to the specific key and then value is returned.

C# Non Generic Hashtable is defined under System.Collections namespace.

Let's have a look at an example:

using System;
using System.Collections;
class HashtableEx {
    public void HashtableExample() {
        //Creating Hashtable
        Hashtable hashtable = new Hashtable();
        //Adding Key Value Pair in Hashtable
        hashtable.Add(1, "This is First");
        hashtable.Add("2", "This is Second");
        hashtable.Add(true, "This is true");
        //Retrieving data DictionaryEntry is Used to retrieve values in Key/Value Form
        foreach(DictionaryEntry data in hashtable) {
            Console.WriteLine("Key: " + data.Key + " Value: " + data.Value);
        }
    }
}

Above program results in the following output:

Key: True Value: This is true
Key: 1 Value: This is First
Key: 2 Value: This is Second

So, from the above example we can conclude that the Key and the Value that are retrieved are in order of the HashCode of their Specific Key.

Here, also note that Key and value can be stored in any type of Data.

These were all the Non Generic way to store the Data in the Collections So from above all the Non Generic Collections we can Conclude some Advantages and Disadvantages of Non Generic type Collection.

Advantages

  1. As there is no restriction on its size, it grows and shrinks dynamically.
  2. We get lots of Method to Add and Remove the elements from the middle of the Collections.
  3. We can store value of any data type in the similar Non Generic Collections.

Disadvantages

  1. Non-generic collections are not type-safe as they operate on object data type so they can store any type of value.
  2. Loosley typed collections can also cause performance overhead, because Boxing and Unboxing happens.

So far, we have seen that the traditional way to store and manage elements i.e an array, but it has a limited size.

In addition, there are non-generic collections that can be used to store and manage elements. However, these collections have limitations as to type safety.

C# Generic Collections solve both of these problems. Since they are type safe and don't have size limitations.

Generic Collections

The Generic Collections are Strongly Typed Collections. These Collections’s Classes only allows one type of value to use. They Eliminate the Type-Mismatch runtime error as well as operations on this type of collections are faster because they don’t require to perform boxing and unboxing while manipulating. So, It is always a preferable and a good choice to use the Generics Collections in C# rather than using the Non-Generic Collections.

The Generic Collection classes are implemented under the System.Collections.Generic namespace.

It Includes:

  1. Stack<T>: Performs Operations similar to Non Generic Stack but it only works on one type of data.
  2. Queue<T>: Similar to Non Generic Queue but queue also works on one type of data.
  3. LinkedList<T>: In LinkedList Both the Previous and Next Node are linked with each other.
  4. SortedList<TKey,TValue>: It works similar to the Non Generic SortedList But it works on one type of data.
  5. List<T>: It works similiar to Non Generic ArrayList but this is only for one type of data.
  6. Dictionary<TKey,TValue>: It Stores the key value pairs of one same type of data. 

Note: Here 'T' is a type of elements that we need to store into them.

Let's take a look at each of them one by one:

C# Generic Stack<T>

Generic stacks contain only the types of values that will be specified during stack creation. It is a dynamic Collection that Grows on adding Elements into it. We can Perform Push() and Pop() For Adding and Removing the elements from the Stack.

C# Generic Stack is defined under System.Collections.Generic namespace.

Let's have a look at an example:

using System;
using System.Collections.Generic
class StackGN {
    public void StackGeneric() {
        //Creating Generic Stack
        Stack < int > stackInt = new Stack < int > ();
        //Pushing Values into Stack
        stackInt.Push(15);
        stackInt.Push(58);
        stackInt.Push(45);
        stackInt.Push(78);
        //Popping value from stack
        stackInt.Pop();
        //Printing Values of stack
        Console.WriteLine("Values inside Stack are:");
        foreach(int values in stackInt) {
            Console.WriteLine(values);
        }
    }
}

Above Program Results in the Following Output:

Values inside Stack are:
45
58
15

So, from the above program we can conclude that the Values that are Retrieved from the Stack are in LIFO Manner. However, the stack<int> will only store integer values, so type safety is maintained.

C# Generic Queue<T>

Queue stores elements in FIFO style (First In First Out), exactly opposite of the Stack<T> collection. Elements in Queue can be added using Enqueue() method and removed using Dequeue() method.

C# Generic Queue is defined under System.Collections.Generic namespace.

Let's have a look at an example:

using System;
using System.Collections.Generic;
class QueueGN {
    public void QueueGeneric() {
        //Creating Queue
        Queue < string > queue = new Queue < string > ();
        //Adding Elements
        queue.Enqueue("This is to be deleted");
        queue.Enqueue("This is First Value");
        queue.Enqueue("This is Second Value");
        queue.Enqueue("This is Third Value");
        //queue.Enqueue(4);   Error
        //Deleting Element
        queue.Dequeue();
        //Printing Elements
        Console.WriteLine("Elements Persent Inside Queue are:");
        foreach(string values in queue) {
            Console.WriteLine(values);
        }
    }
}

Above program results in the following output,

Elements Persent Inside Queue are:
This is First Value
This is Second Value
This is Third Value

From the above example we can conclude that when in Queue having type string if we try to add integer value then it gives Compile time error.

C# Generic LinkedList<T>

A Generic LinkedList Stores element in non Contigous manner. The elements in a linked list are linked with each other. There are various method used to add the element at various positions such as AddAfter(), AddBefore(), AddFirst(), AddLast() and many more.

C# Generic LinkedList is defined under System.Collections.Generic namespace.

Let's have a look at an example:

using System;
using System.Collections.Generic;
class LinkedListGN {
    public void LinkedListGeneric() {
        //Creating linked List
        LinkedList < string > linkedList = new LinkedList < string > ();
        //Adding Elements
        linkedList.AddLast("Nita");
        linkedList.AddFirst("Karishma");
        linkedList.AddLast("Shilpa");
        linkedList.AddLast(“Nita”);
        //Printing the Values
        Console.WriteLine("Elements in the LinkedList are:");
        foreach(string values in linkedList) {
            Console.WriteLine(values);
        }
    }
}

Above program results in the following output:

Elements in the LinkedList are:
Karishma
Nita
Shilpa
Nita

So, from the above example we can conclude that we can have duplicate value as well in Generic LinkedList but it should be of same data type.

C# Generic SortedList<TKey,TValue>

SortedList Stores the data in key and Value Pairs. We can access the element by using key or using index of the element. As it is Generic type so it will Contains only one type of key and one type of values.

C# Generic LinkedList is defined under System.Collections.Generic namespace.

Let's have a look at an example:

using System;
using System.Collections.Generic;
class SortedListGN {
    public void SortedListGeneric() {
        //creating Generic Sorted List
        SortedList < int, string > valuePairs = new SortedList < int, string > ();
        //Adding Values
        valuePairs.Add(2, "Komal");
        valuePairs.Add(1, "Lalit");
        valuePairs.Add(3, "Mohit");
        //valuePairs.Add(3, "Mohit"); Error Duplicate Key
        //Printing Values
        Console.WriteLine("Values in Generic Sorted List are:");
        foreach(KeyValuePair < int, string > entry in valuePairs) {
            Console.WriteLine(entry.Key + ":" + entry.Value);
        }
    }
}

Above program results in the following output:

Values in Generic Sorted List are:
1:Lalit
2:Komal
3:Mohit

From the above example we can conclude that Generic SortedList can contain Same type of key and their values will also contains the same type of elements but we can’t add the duplicate key.

C# Generic List<T>

Generic List is Pretty much similar to ArrayList in Non Generic Collections. Elements will be of one defined type and can be accessed through index. Elements can be Added using Add() method. Doesn’t need to perform boxing and unboxing due to generic type.

C# Generic List is defined under System.Collections.Generic namespace.

Let's have a look at an example:

using System;
using System.Collections.Generic;
class ListGN {
    public void ListGeneric() {
        //Creating a List
        List < string > Names = new List < string > ();
        //Adding Values to List
        Names.Add("Mark");
        Names.Add("Olie");
        Names.Add("Berik");
        //Displaying Names
        Console.WriteLine("Elements in List are: ");
        foreach(string names in Names) {
            Console.WriteLine(names);
        }
    }
}

Above program results in the following output:

Elements in List are:
Mark
Olie
Berik

From above example we can conclude that Generic List works in Similiar way as Array and ArrayList works but its Generic type so, type Safety and Size Limitation will not be an issue while working with Lists.

C# Generic Dictionary<TKey,TValue>

Generic Dictionary Stores the data in key and Value Pairs. Key should not be null when storing values, and it should be unique. There will be no order of storing the values in Dictionary.

C# Generic Dictionary is defined under System.Collections.Generic namespace.

Let's have a look at an example:

using System;
using System.Collection.Generic;
class DictionaryGN {
    public void DictionaryGeneric() {
        //Creating Dictionary
        Dictionary < int, string > valuePairs = new Dictionary < int, string > ();
        //Adding Values
        valuePairs.Add(1, "This is First Record");
        valuePairs.Add(2, "This is Second Record");
        valuePairs.Add(3, "This is Third Record");
        //Printing Values
        Console.WriteLine("Keys and their values in Dictionary are:");
        foreach(KeyValuePair < int, string > pair in valuePairs) {
            Console.WriteLine("Key: " + pair.Key + " Value: " + pair.Value);
        }
    }
}

Above Program Results in the Following Output:

Keys and their values in Dictionary are:
Key: 1 Value: This is First Record
Key: 2 Value: This is Second Record
Key: 3 Value: This is Third Record

So, this is how we can add and retrieve the elements from Generic Dictionary, Where we have mentioned key as an integer and value as a string and retrieved their value using keyValuePair<Tkey,Tvalue>, Which is used to store keyValuePair.

Thus we have used Generic collections for storing and manipulating data, therefore, based on that, we have concluded that Generic way is type-safe, i.e. we will get a compile time error if we use a different type of data rather than the one specified. It also makes the application run faster.

Conclusion

In this article, we first examined arrays and their implementation and then discussed their advantages and disadvantages. Then, in order to solve the problem of Arrays, i.e. size limitation, we looked into Non Generic Collections, which is efficient in solving the size limitation issue, but they have serious issues with type-safety. Then, we looked into Generic Collections that solve both the problems and are extremely efficient and flexible at the same time.

So, if you find this article helpful kindly share it with your friends