Mastering C# Part 5.2 Collections
Collection Namespace

Hi, I'm Nishant Banjade, a Software Engineer at Ellucian, committed to transforming education through technology. With expertise in developing CRM systems, plugins, workflows, APIs, and customized solutions for Higher Education, I bring a strong focus on innovation and efficiency. Currently, I'm honing my skills in full-stack software development, system design, data structures, algorithms, and Microsoft Dynamics 365.
I am a skilled software developer with expertise in C#/.NET, D365, JavaScript, TypeScript, ReactJS, SQL, and cloud technologies like AWS, along with experience in RabbitMQ, Docker, MUI, Tailwind, JIRA, and Git.
Array
An Array is a fixed-size collection of elements of the same type. It is part of the System namespace. Arrays are useful when you know the number of elements in advance and do not require resizing.
Key Features of an Array
Fixed Size: Size is determined at creation and cannot be changed.
Type-Safe: All elements must be of the same type.
Fast Access: Directly access elements using an index.
Best Use Case: When the number of elements is fixed or known beforehand.
Example of an Array
using System;
class Program
{
static void Main()
{
// Declare and initialize an array
int[] numbers = new int[5] { 1, 2, 3, 4, 5 };
// Access and modify elements
Console.WriteLine($"First element: {numbers[0]}"); // Output: 1
numbers[0] = 10;
// Iterate through the array
Console.WriteLine("\\nArray Elements:");
foreach (var num in numbers)
{
Console.WriteLine(num);
}
}
}
Output:
First element: 1
Array Elements:
10
2
3
4
5
ArrayList
An ArrayList is a dynamically resizable, non-generic collection in the System.Collections namespace. It can store elements of different types. However, since it is non-generic, type safety is not enforced.
Key Features of an ArrayList
Dynamic Resizing: Automatically grows as new elements are added.
Type-Unsafe: Can store objects of any type, leading to potential runtime errors.
Slower than Array: Performance is slightly slower because of boxing/unboxing for value types.
Best Use Case: When you need a dynamic collection and are using .NET versions older than 2.0 (otherwise, prefer
List<T>).
Example of an ArrayList
using System;
using System.Collections;
class Program
{
static void Main()
{
// Create an ArrayList
ArrayList list = new ArrayList();
// Add elements of different types
list.Add(1); // int
list.Add("Two"); // string
list.Add(3.0); // double
// Access elements
Console.WriteLine($"First element: {list[0]}"); // Output: 1
// Iterate through the ArrayList
Console.WriteLine("\\nArrayList Elements:");
foreach (var item in list)
{
Console.WriteLine(item);
}
// Remove an element
list.Remove("Two");
Console.WriteLine("\\nAfter Removal:");
foreach (var item in list)
{
Console.WriteLine(item);
}
}
}
Output:
First element: 1
ArrayList Elements:
1
Two
3
After Removal:
1
3
Comparison of Array vs ArrayList
| Feature | Array | ArrayList |
| Type-Safe | Yes, only one type of elements | No, stores elements as object |
| Resizable | No, fixed size | Yes, dynamic resizing |
| Performance | Faster | Slower due to boxing/unboxing |
| Namespace | System | System.Collections |
| Best Alternative | Fixed data collection | Use List<T> for type safety |
When to Use Which?
Use
Array:When the size is fixed.
When type safety and performance are critical.
Use
ArrayList:When the size of the collection changes frequently.
If working in older .NET versions (prefer
List<T>in newer versions).
For modern applications, List<T> is usually preferred over ArrayList.
HashTable
The Hashtable class in C# is part of the System.Collections namespace and represents a collection of key-value pairs. It is non-generic, meaning it can store keys and values of any data type, and provides fast lookup, insertion, and deletion operations. However, since it's not type-safe, it's less commonly used in modern C# applications where the Dictionary<TKey, TValue> class (which is generic) is preferred for better performance and type safety.
Key Features of Hashtable
Non-Generic: Stores objects of any type (not type-safe).
Key-Value Pairs: It stores data as key-value pairs, where the key is unique.
Hashing: Uses a hash function to determine the index of elements for fast access.
Thread-Safety: Not thread-safe by default. For thread-safety, use
Hashtable.Synchronized.Best Use Case: When you need to store key-value pairs but don't care about type safety (though
Dictionary<TKey, TValue>is a better alternative).
Basic Operations with Hashtable
Add: Adds a key-value pair to the Hashtable.
ContainsKey: Checks if a particular key exists.
Remove: Removes a key-value pair.
Indexer: Allows access to values based on keys.
Example of Hashtable
using System;
using System.Collections;
class Program
{
static void Main()
{
// Create a Hashtable
Hashtable ht = new Hashtable();
// Add key-value pairs to the Hashtable
ht.Add(1, "Apple");
ht.Add(2, "Banana");
ht.Add(3, "Cherry");
// Access values using keys
Console.WriteLine($"Key 1: {ht[1]}"); // Output: Apple
// Check if a key exists
Console.WriteLine($"Contains key 2: {ht.ContainsKey(2)}"); // Output: True
// Remove a key-value pair
ht.Remove(2);
// Iterate through the Hashtable
Console.WriteLine("\\nHashtable Contents:");
foreach (DictionaryEntry entry in ht)
{
Console.WriteLine($"Key: {entry.Key}, Value: {entry.Value}");
}
}
}
Output:
Key 1: Apple
Contains key 2: True
Hashtable Contents:
Key: 1, Value: Apple
Key: 3, Value: Cherry
Important Methods and Properties
Add(key, value): Adds a key-value pair.
ContainsKey(key): Returns
trueif the key exists.ContainsValue(value): Returns
trueif the value exists.Remove(key): Removes the entry with the specified key.
Clear(): Removes all key-value pairs from the Hashtable.
Count: Returns the number of key-value pairs in the Hashtable.
Item[key]: Allows access to the value associated with the specified key.
When to Use Hashtable
Legacy Code: If you are maintaining or working with older code that uses
Hashtable.Type Safety Not Important: When type safety is not required and flexibility with key and value types is needed.
Thread-Safety: If you need thread-safe operations, you can use
Hashtable.Synchronized.
However, for modern applications, Dictionary<TKey, TValue> is usually the better choice because it's type-safe, faster, and more flexible.
Hashtable vs Dictionary<TKey, TValue>
| Feature | Hashtable | Dictionary<TKey, TValue> |
| Type-Safe | No | Yes |
| Performance | Slightly slower (due to boxing/unboxing) | Faster (type-safe) |
| Thread-Safety | Not thread-safe by default, can use Synchronized | Not thread-safe by default, can use ConcurrentDictionary |
| Generic Support | No | Yes |
| Use Case | Legacy code, untyped collections | Modern, type-safe collections |
Conclusion
Use
Hashtablewhen working with older code or when you need flexibility with different data types (but keep in mind it's not type-safe).Use
Dictionary<TKey, TValue>for modern, type-safe, and high-performance code.
Hashtable Example with More Operations
using System;
using System.Collections;
class Program
{
static void Main()
{
// Create a new Hashtable instance
Hashtable ht = new Hashtable();
// Add some key-value pairs
ht.Add("1", "Apple");
ht.Add("2", "Banana");
ht.Add("3", "Cherry");
ht.Add("4", "Date");
ht.Add("5", "Elderberry");
// Display the count of items in the Hashtable
Console.WriteLine($"Hashtable contains {ht.Count} items.");
// Accessing an element using a key (via the indexer)
Console.WriteLine($"Element with key '3': {ht["3"]}");
// Checking if a specific key exists in the Hashtable
Console.WriteLine($"Contains key '2': {ht.ContainsKey("2")}");
// Checking if a specific value exists in the Hashtable
Console.WriteLine($"Contains value 'Banana': {ht.ContainsValue("Banana")}");
// Removing an element by key
ht.Remove("4");
Console.WriteLine("\\nRemoved key '4'. Now the Hashtable contains:");
// Iterating over the Hashtable (using DictionaryEntry)
foreach (DictionaryEntry entry in ht)
{
Console.WriteLine($"Key: {entry.Key}, Value: {entry.Value}");
}
// Accessing a value that doesn't exist (throws KeyNotFoundException)
try
{
Console.WriteLine($"Element with key '10': {ht["10"]}"); // Key doesn't exist
}
catch (KeyNotFoundException ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
// Using the indexer to update an existing value
ht["1"] = "Avocado";
Console.WriteLine("\\nUpdated element with key '1':");
Console.WriteLine($"Key: 1, New Value: {ht["1"]}");
// Clear the Hashtable
ht.Clear();
Console.WriteLine($"\\nHashtable cleared. Now it contains {ht.Count} items.");
}
}
Explanation of Operations in the Code
Creating the Hashtable:
Hashtable ht = new Hashtable();This initializes an empty
Hashtable.Adding Elements:
ht.Add("1", "Apple"); ht.Add("2", "Banana");Adds key-value pairs to the Hashtable.
Accessing Elements:
Console.WriteLine($"Element with key '3': {ht["3"]}");Retrieves the value associated with the specified key (
"3"in this case).Checking if Key/Value Exists:
Console.WriteLine($"Contains key '2': {ht.ContainsKey("2")}"); Console.WriteLine($"Contains value 'Banana': {ht.ContainsValue("Banana")}");These check if the Hashtable contains a particular key or value.
Removing an Element:
ht.Remove("4");Removes the key-value pair with the specified key (
"4"in this case).Iterating Over the Hashtable:
foreach (DictionaryEntry entry in ht) { Console.WriteLine($"Key: {entry.Key}, Value: {entry.Value}"); }Iterates over all key-value pairs in the
Hashtable.Handling Missing Keys:
try { Console.WriteLine($"Element with key '10': {ht["10"]}"); } catch (KeyNotFoundException ex) { Console.WriteLine($"Error: {ex.Message}"); }Accessing a key that does not exist throws a
KeyNotFoundException, which we catch and display a message.Updating an Element:
ht["1"] = "Avocado";The value associated with an existing key can be updated using the indexer.
Clearing the Hashtable:
ht.Clear();Removes all key-value pairs from the Hashtable.
Expected Output
Hashtable contains 5 items.
Element with key '3': Cherry
Contains key '2': True
Contains value 'Banana': True
Removed key '4'. Now the Hashtable contains:
Key: 1, Value: Apple
Key: 2, Value: Banana
Key: 3, Value: Cherry
Key: 5, Value: Elderberry
Error: Key not found.
Updated element with key '1':
Key: 1, New Value: Avocado
Hashtable cleared. Now it contains 0 items.
Summary of Key Points
Add: Adds a key-value pair.ContainsKey/ContainsValue: Checks if a key or value exists.Remove: Removes a key-value pair by key.Iteration: Can be done using a
foreachloop overDictionaryEntry.Indexer (
[]): Accesses and modifies values associated with a key.Clear: Clears all elements in the Hashtable.
BitArray class
The BitArray class in C# is part of the System.Collections namespace and represents a collection of bits (binary values, 0 or 1). It's useful when you need to efficiently store and manipulate a large number of boolean values or binary data. Each element of a BitArray is a single bit, so it provides a memory-efficient way to handle binary data.
Key Features of BitArray
Memory Efficiency: It uses a single bit per element, making it more memory-efficient than using other collections like
bool[].Indexable: You can access and modify individual bits using an index.
Bitwise Operations: It supports bitwise logical operations such as AND, OR, XOR, and NOT.
Fixed Size: Once created, the size of a
BitArrayis fixed, but you can resize it if needed.Automatic Resizing: You can increase the size of the
BitArray, but it does not shrink automatically.Thread-Safety: It is not thread-safe by default.
Basic Operations with BitArray
Set: Set a specific bit to
trueorfalse.Get: Get the value of a bit (either
trueorfalse).AND, OR, XOR: Perform bitwise operations.
Count: Get the number of bits in the
BitArray.
Example of BitArray Usage
Here's a simple example demonstrating how to create and manipulate a BitArray in C#:
using System;
using System.Collections;
class Program
{
static void Main()
{
// Create a BitArray with 5 elements (bits) initialized to false (0)
BitArray bits = new BitArray(5);
// Set individual bits
bits[0] = true; // Set bit at index 0 to true (1)
bits[1] = true; // Set bit at index 1 to true (1)
// Display the BitArray
Console.WriteLine("BitArray after setting bits:");
for (int i = 0; i < bits.Length; i++)
{
Console.Write(bits[i] ? "1 " : "0 ");
}
Console.WriteLine(); // Output: 1 1 0 0 0
// Perform bitwise operations
BitArray otherBits = new BitArray(new bool[] { true, false, true, false, true });
// AND operation
BitArray andResult = bits.And(otherBits);
Console.WriteLine("Result of AND operation:");
for (int i = 0; i < andResult.Length; i++)
{
Console.Write(andResult[i] ? "1 " : "0 ");
}
Console.WriteLine(); // Output: 1 0 0 0 0
// OR operation
BitArray orResult = bits.Or(otherBits);
Console.WriteLine("Result of OR operation:");
for (int i = 0; i < orResult.Length; i++)
{
Console.Write(orResult[i] ? "1 " : "0 ");
}
Console.WriteLine(); // Output: 1 1 1 0 1
// XOR operation
BitArray xorResult = bits.Xor(otherBits);
Console.WriteLine("Result of XOR operation:");
for (int i = 0; i < xorResult.Length; i++)
{
Console.Write(xorResult[i] ? "1 " : "0 ");
}
Console.WriteLine(); // Output: 0 1 1 0 1
// NOT operation (flip the bits)
BitArray notResult = bits.Not();
Console.WriteLine("Result of NOT operation:");
for (int i = 0; i < notResult.Length; i++)
{
Console.Write(notResult[i] ? "1 " : "0 ");
}
Console.WriteLine(); // Output: 0 0 1 1 1
}
}
Explanation of Operations
Creating a
BitArray:A
BitArrayis created with 5 elements. Initially, all bits arefalse(0).BitArray bits = new BitArray(5);
Setting Bits:
You can set individual bits using the indexer (
[]).bits[0] = true;sets the bit at index0to1.
Displaying the
BitArray:- A loop is used to print the bits in the array, displaying
1or0.
- A loop is used to print the bits in the array, displaying
Bitwise Operations:
AND: The
Andmethod performs a bitwise AND operation between twoBitArrayobjects.OR: The
Ormethod performs a bitwise OR operation.XOR: The
Xormethod performs a bitwise XOR operation.NOT: The
Notmethod flips all the bits in theBitArray.
Output of Bitwise Operations:
- Each operation modifies the bits and the result is displayed.
Expected Output
BitArray after setting bits:
1 1 0 0 0
Result of AND operation:
1 0 0 0 0
Result of OR operation:
1 1 1 0 1
Result of XOR operation:
0 1 1 0 1
Result of NOT operation:
0 0 1 1 1
Key Methods of BitArray
And(BitArray value): Performs a bitwise AND between the currentBitArrayand anotherBitArray.Or(BitArray value): Performs a bitwise OR between the currentBitArrayand anotherBitArray.Xor(BitArray value): Performs a bitwise XOR between the currentBitArrayand anotherBitArray.Not(): Flips all the bits in theBitArray.Length: Gets the number of bits in theBitArray.Set(int index, bool value): Sets the bit at the specified index to a specified value (trueorfalse).Get(int index): Retrieves the value of the bit at the specified index.
When to Use BitArray
Memory Efficiency: When you need to work with a large number of boolean values and memory efficiency is important.
Bitwise Operations: If you need to perform bitwise logical operations on collections of bits (such as in networking, compression algorithms, or cryptography).
Flag Management: When dealing with flags or binary options (e.g., checking permissions).
SortedList
The SortedList<TKey, TValue> class in C# is part of the System.Collections.Generic namespace. It represents a collection of key/value pairs that are sorted by the keys and allows fast retrieval based on the key. It is similar to a Dictionary<TKey, TValue>, but with the added feature that the keys are automatically sorted in ascending order.
Key Features of SortedList<TKey, TValue>
Sorted by Keys: The collection is sorted by the keys. This is useful when you need to maintain an ordered collection of items.
Efficient Lookup:
SortedListprovides fast lookup, similar to aDictionary, but with the benefit of automatic ordering of keys.Index Access: You can access both the key-value pairs and individual elements by their index, not just by key.
Performance: Insertions, deletions, and lookups are fast but not as fast as a
Dictionary. The insertion might take longer due to the need to maintain the order.
Basic Operations with SortedList<TKey, TValue>
Add: Add a key-value pair to the list.
Get/Set: Retrieve or modify the value associated with a specific key.
Remove: Remove a key-value pair by key.
Index Access: Access items using an index or a key.
Example of SortedList<TKey, TValue> Usage
Here's a simple example demonstrating how to create and manipulate a SortedList:
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
// Creating a SortedList of integers with string values
SortedList<int, string> sortedList = new SortedList<int, string>();
// Adding elements
sortedList.Add(3, "Three");
sortedList.Add(1, "One");
sortedList.Add(2, "Two");
// Display the SortedList (automatically sorted by key)
Console.WriteLine("SortedList contents:");
foreach (var kvp in sortedList)
{
Console.WriteLine($"Key: {kvp.Key}, Value: {kvp.Value}");
}
// Accessing value by key
Console.WriteLine("\\nValue for key 2: " + sortedList[2]); // Output: Two
// Modifying a value by key
sortedList[2] = "Updated Two";
Console.WriteLine("\\nUpdated value for key 2: " + sortedList[2]); // Output: Updated Two
// Removing a key-value pair
sortedList.Remove(1);
Console.WriteLine("\\nAfter removing key 1:");
foreach (var kvp in sortedList)
{
Console.WriteLine($"Key: {kvp.Key}, Value: {kvp.Value}");
}
// Checking if a key exists
if (sortedList.ContainsKey(3))
{
Console.WriteLine("\\nKey 3 exists in the SortedList.");
}
// Accessing value by index
Console.WriteLine("\\nElement at index 0:");
var elementAtIndex = sortedList.ElementAt(0); // Get element at index 0
Console.WriteLine($"Key: {elementAtIndex.Key}, Value: {elementAtIndex.Value}");
}
}
Explanation of Operations
Creating a
SortedList:A
SortedList<int, string>is created to store integer keys and string values.SortedList<int, string> sortedList = new SortedList<int, string>();
Adding Elements:
The
Addmethod is used to insert key-value pairs.sortedList.Add(3, "Three");
Displaying the
SortedList:- A
foreachloop is used to display all elements, and the list will automatically be sorted by key.
- A
Accessing by Key:
- The value associated with a specific key is accessed directly using
sortedList[key].
- The value associated with a specific key is accessed directly using
Modifying Values:
- You can modify the value for a given key using the indexer (
sortedList[key] = newValue).
- You can modify the value for a given key using the indexer (
Removing Elements:
The
Removemethod is used to delete a key-value pair by key.sortedList.Remove(1);
Checking for Key Existence:
- The
ContainsKeymethod checks if a specific key exists in the list.
- The
Accessing by Index:
- The
ElementAt(index)method is used to get the element at a particular index in the sorted list.
- The
Expected Output
SortedList contents:
Key: 1, Value: One
Key: 2, Value: Two
Key: 3, Value: Three
Value for key 2: Two
Updated value for key 2: Updated Two
After removing key 1:
Key: 2, Value: Updated Two
Key: 3, Value: Three
Key 3 exists in the SortedList.
Element at index 0:
Key: 2, Value: Updated Two
Key Methods of SortedList<TKey, TValue>
Add(TKey key, TValue value): Adds a key-value pair to the list.Remove(TKey key): Removes the key-value pair with the specified key.ContainsKey(TKey key): Checks if a key exists in the list.ContainsValue(TValue value): Checks if a value exists in the list.Item[TKey key]: Indexer to get or set a value by key.Count: Gets the number of key-value pairs in the list.Keys: Gets a collection of the keys in the list.Values: Gets a collection of the values in the list.
When to Use SortedList<TKey, TValue>
Ordered Collection: When you need to maintain a collection of items that are always sorted by key.
Fast Lookup: If you need to look up values by key in an ordered collection with a small overhead on insertions and deletions.
Efficient for Smaller Collections: While
SortedListis efficient in terms of sorting and lookups, it may not be as efficient asDictionaryfor larger collections because of the overhead required to maintain sorting.