Collections in C# are data structures that allow you to store and manipulate groups of related objects. They provide a way to organize and manage data efficiently. C# provides a rich set of collection classes that are part of the .NET Framework.
There are two types of collections
1- Generic Collection
2- Non - Generic Collection
The System.Collections namespace contains the non-generic collection types and System.Collections.Generic namespace includes generic collection types.
In most cases, it is recommended to use the generic collections because they perform faster than non-generic collections and also minimize exceptions by giving compile-time errors.
1- Generic Collections :
1 - A) List <T>
: The List<T> class is a dynamic array that can grow or shrink in size. It allows you to store elements of a specified type T and provides methods to add, remove, and access elements by index. It is one of the most versatile and widely used collection types in C#.
Example :
List<int> numbers = new List<int>();
numbers.Add(1);
numbers.Add(2);
numbers.Add(3);
1- B) Dictionary<TKey,TValue>
: The Dictionary<TKey, TValue> class represents a collection of key-value pairs. It allows you to store elements based on a unique key and provides fast lookup and retrieval of values based on the key. Keys must be unique within the dictionary.
Example :
Dictionary<string, int> ages = new Dictionary<string, int>();
ages.Add("John", 25);
ages.Add("Jane", 30);
ages.Add("Alice", 35);
1- C) SortedList<TKey,TValue>
: The SortedList<TKey,TValue> class in C# is a collection that represents a sorted list of key-value pairs. It is similar to the Dictionary<TKey,TValue> class, but with one key difference - the elements in a SortedList<TKey,TValue> are always sorted by their keys.
Purpose and Functionality :
Purpose and FunctionalityThe main purpose of the SortedList<TKey,TValue> class is to provide a data structure that allows efficient searching and retrieval of elements based on their keys, while maintaining the elements in a sorted order. This can be useful in scenarios where you need to maintain a collection of items that need to be accessed in a specific order.
The SortedList<TKey,TValue> class is implemented as an array of key-value pairs, where each pair represents an element in the list. The keys are used to determine the order of the elements, and the values are associated with the keys.
Example :
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
// Create a new instance of SortedList<TKey,TValue>
SortedList<int, string> sortedList = new SortedList<int, string>();
// Add elements to the sorted list
sortedList.Add(3, "Apple");
sortedList.Add(1, "Banana");
sortedList.Add(2, "Orange");
// Iterate over the sorted list
foreach (KeyValuePair<int, string> kvp in sortedList)
{
Console.WriteLine($"Key: {kvp.Key}, Value: {kvp.Value}");
}
}
}
1-D ) Queue<T>
: The Queue<T> class represents a first-in, first-out (FIFO) collection of elements. It allows you to add elements to the end of the queue and remove elements from the beginning. It is commonly used in scenarios where the order of elements is important.
Let's consider an example where we have a queue of integers representing a line of customers waiting to be served at a bank. We can create a Queue<int> object and add customers to the queue using the Enqueue method:
Queue<int> customerQueue = new Queue<int>();
customerQueue.Enqueue(1);
customerQueue.Enqueue(2);
customerQueue.Enqueue(3);
In this example, we added three customers to the queue with IDs 1, 2, and 3. The Enqueue method adds elements to the end of the queue.
To process the customers in the order they arrived, we can use the Dequeue method to remove and retrieve the first customer from the queue:
int firstCustomer = customerQueue.Dequeue();
Console.WriteLine("Serving customer {0}", firstCustomer);
The output will be:
Serving customer 1
After serving the first customer, the queue will now contain the remaining customers with IDs 2 and 3. We can continue processing the queue by calling Dequeue again.
Why Use Queue<T>
The Queue<T> class provides an efficient way to manage a collection of items in a specific order. Here are some reasons why you might choose to use a Queue<T>:
- FIFO Order: Queue<T> ensures that items are processed in the order they were added, making it suitable for scenarios where the order of operations matters.
- Real-world Analogies: The concept of a queue is similar to real-world scenarios like waiting in line at a ticket counter or processing tasks in a background job queue. Using a Queue<T> can help model and solve problems that involve sequential processing.
- Efficient Operations: Queue<T> provides efficient methods for adding and removing elements from both ends of the queue. The Enqueue and Dequeue operations have a time complexity of O(1), making them fast and suitable for large datasets.
- Thread Safety: Queue<T> is designed to be used in multi-threaded scenarios. It provides built-in synchronization mechanisms to ensure safe access and modification of the queue from multiple threads.
- Integration with Other Data Structures: Queue<T> can be used in combination with other data structures to build more complex algorithms. For example, you can use a queue to implement a breadth-first search (BFS) algorithm.
1- E ) Stack<T> :
The Stack<T> class represents a last-in, first-out (LIFO) collection of elements. It allows you to add elements to the top of the stack and remove elements from the top. It is commonly used in scenarios where the order of elements is important, but in reverse compared to a queue.
Example :
Stack<string> stack = new Stack<string>();
stack.Push("John");
stack.Push("Jane");
string topPerson = stack.Pop(); // Removes and returns "Jane"
Other Useful Methods :
The Stack<T> class provides several other useful methods, such as Peek, Count, and Contains.
- The Peek method returns the topmost element from the stack without removing it. It is useful when you want to retrieve the top element without modifying the stack.
- The Count property returns the number of elements in the stack.
- The Contains method checks whether a specific element exists in the stack.
Why Use the Stack<T> Class? :
The Stack<T> class is useful in various scenarios where you need to store and retrieve elements in a last-in, first-out manner. Here are a few examples:
- Undo/Redo functionality: When implementing undo/redo functionality in an application, you can use a stack to store the state of objects. Each time a user performs an action, you push the current state onto the stack. When the user wants to undo an action, you pop the topmost state from the stack.
- Function call stack: In programming languages, function calls are typically managed using a stack. Each time a function is called, its state is pushed onto the stack. When the function completes, its state is popped from the stack, allowing the program to return to the previous function.
- Parsing expressions: When parsing mathematical expressions or evaluating postfix expressions, a stack can be used to store operators or operands temporarily.
- Backtracking algorithms: Backtracking algorithms often use a stack to keep track of the current path and backtrack when necessary.
1- F ) Hashset<T> The HashSet<T> class in C# is a collection that stores unique elements in no particular order. It is part of the System.Collections.Generic namespace and provides efficient lookup, insertion, and removal operations. HashSet<T> is commonly used when you need to store a collection of items without any duplicates.
HashSet<T> Example :
Let's consider an example to illustrate the usage of HashSet<T>. Suppose we have a list of students and we want to keep track of the unique courses they are enrolled in. We can use a HashSet<T> to store the course names for each student.
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
HashSet<string> courses = new HashSet<string>();
// Add courses for student 1
courses.Add("Math");
courses.Add("Science");
courses.Add("English");
// Add courses for student 2
courses.Add("Science");
courses.Add("History");
// Add courses for student 3
courses.Add("Math");
courses.Add("Art");
// Print the unique courses for all students
foreach (string course in courses)
{
Console.WriteLine(course);
}
}
}
In this example, we create a HashSet<string> called courses to store the unique course names. We add courses for three different students, and since HashSet<T> does not allow duplicates, only the unique courses are stored. Finally, we iterate over the HashSet and print the unique courses.
The output of the above code will be:
Math
Science
English
History
Art
As you can see, the HashSet<T> automatically eliminates duplicate entries, ensuring that each course is stored only once.
Why Use HashSet<T>? :
HashSet<T> offers several advantages over other collection types in C#. Here are some reasons why you might choose to use HashSet<T>:
- Efficient Lookup: HashSet<T> provides constant-time performance for operations like adding, removing, and checking for the presence of an element. This makes it suitable for scenarios where fast lookup is required.
- Automatic Deduplication: HashSet<T> automatically eliminates duplicate elements, ensuring that each element is unique within the collection. This can be useful when dealing with data that should not have duplicates.
- Set Operations: HashSet<T> supports set operations such as union, intersection, and difference. These operations can be performed efficiently, making HashSet<T> a good choice when dealing with set-based operations.
- Flexible Type Support: HashSet<T> can store elements of any type, as long as the type implements the necessary equality and hashing methods. This allows you to use HashSet<T> with custom types and not just the built-in types.
- Performance: HashSet<T> is optimized for performance and can handle large collections efficiently. It uses a hash-based data structure internally, which provides fast access to elements.