Skip to main content

Command Palette

Search for a command to run...

Mastering C# Part 5.2 Collections

Collection Namespace

Updated
15 min read
Mastering C# Part 5.2 Collections
N

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

  1. Fixed Size: Size is determined at creation and cannot be changed.

  2. Type-Safe: All elements must be of the same type.

  3. Fast Access: Directly access elements using an index.

  4. 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

  1. Dynamic Resizing: Automatically grows as new elements are added.

  2. Type-Unsafe: Can store objects of any type, leading to potential runtime errors.

  3. Slower than Array: Performance is slightly slower because of boxing/unboxing for value types.

  4. 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

FeatureArrayArrayList
Type-SafeYes, only one type of elementsNo, stores elements as object
ResizableNo, fixed sizeYes, dynamic resizing
PerformanceFasterSlower due to boxing/unboxing
NamespaceSystemSystem.Collections
Best AlternativeFixed data collectionUse List<T> for type safety

When to Use Which?

  1. Use Array:

    • When the size is fixed.

    • When type safety and performance are critical.

  2. 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

  1. Non-Generic: Stores objects of any type (not type-safe).

  2. Key-Value Pairs: It stores data as key-value pairs, where the key is unique.

  3. Hashing: Uses a hash function to determine the index of elements for fast access.

  4. Thread-Safety: Not thread-safe by default. For thread-safety, use Hashtable.Synchronized.

  5. 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

  1. Add(key, value): Adds a key-value pair.

  2. ContainsKey(key): Returns true if the key exists.

  3. ContainsValue(value): Returns true if the value exists.

  4. Remove(key): Removes the entry with the specified key.

  5. Clear(): Removes all key-value pairs from the Hashtable.

  6. Count: Returns the number of key-value pairs in the Hashtable.

  7. Item[key]: Allows access to the value associated with the specified key.


When to Use Hashtable

  1. Legacy Code: If you are maintaining or working with older code that uses Hashtable.

  2. Type Safety Not Important: When type safety is not required and flexibility with key and value types is needed.

  3. 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>

FeatureHashtableDictionary<TKey, TValue>
Type-SafeNoYes
PerformanceSlightly slower (due to boxing/unboxing)Faster (type-safe)
Thread-SafetyNot thread-safe by default, can use SynchronizedNot thread-safe by default, can use ConcurrentDictionary
Generic SupportNoYes
Use CaseLegacy code, untyped collectionsModern, type-safe collections

Conclusion

  • Use Hashtable when 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

  1. Creating the Hashtable:

     Hashtable ht = new Hashtable();
    

    This initializes an empty Hashtable.

  2. Adding Elements:

     ht.Add("1", "Apple");
     ht.Add("2", "Banana");
    

    Adds key-value pairs to the Hashtable.

  3. Accessing Elements:

     Console.WriteLine($"Element with key '3': {ht["3"]}");
    

    Retrieves the value associated with the specified key ("3" in this case).

  4. 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.

  5. Removing an Element:

     ht.Remove("4");
    

    Removes the key-value pair with the specified key ("4" in this case).

  6. 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.

  7. 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.

  8. Updating an Element:

     ht["1"] = "Avocado";
    

    The value associated with an existing key can be updated using the indexer.

  9. 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 foreach loop over DictionaryEntry.

  • 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

  1. Memory Efficiency: It uses a single bit per element, making it more memory-efficient than using other collections like bool[].

  2. Indexable: You can access and modify individual bits using an index.

  3. Bitwise Operations: It supports bitwise logical operations such as AND, OR, XOR, and NOT.

  4. Fixed Size: Once created, the size of a BitArray is fixed, but you can resize it if needed.

  5. Automatic Resizing: You can increase the size of the BitArray, but it does not shrink automatically.

  6. Thread-Safety: It is not thread-safe by default.

Basic Operations with BitArray

  • Set: Set a specific bit to true or false.

  • Get: Get the value of a bit (either true or false).

  • 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

  1. Creating a BitArray:

    • A BitArray is created with 5 elements. Initially, all bits are false (0).

    • BitArray bits = new BitArray(5);

  2. Setting Bits:

    • You can set individual bits using the indexer ([]).

    • bits[0] = true; sets the bit at index 0 to 1.

  3. Displaying the BitArray:

    • A loop is used to print the bits in the array, displaying 1 or 0.
  4. Bitwise Operations:

    • AND: The And method performs a bitwise AND operation between two BitArray objects.

    • OR: The Or method performs a bitwise OR operation.

    • XOR: The Xor method performs a bitwise XOR operation.

    • NOT: The Not method flips all the bits in the BitArray.

  5. 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 current BitArray and another BitArray.

  • Or(BitArray value): Performs a bitwise OR between the current BitArray and another BitArray.

  • Xor(BitArray value): Performs a bitwise XOR between the current BitArray and another BitArray.

  • Not(): Flips all the bits in the BitArray.

  • Length: Gets the number of bits in the BitArray.

  • Set(int index, bool value): Sets the bit at the specified index to a specified value (true or false).

  • Get(int index): Retrieves the value of the bit at the specified index.

When to Use BitArray

  1. Memory Efficiency: When you need to work with a large number of boolean values and memory efficiency is important.

  2. Bitwise Operations: If you need to perform bitwise logical operations on collections of bits (such as in networking, compression algorithms, or cryptography).

  3. 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>

  1. Sorted by Keys: The collection is sorted by the keys. This is useful when you need to maintain an ordered collection of items.

  2. Efficient Lookup: SortedList provides fast lookup, similar to a Dictionary, but with the benefit of automatic ordering of keys.

  3. Index Access: You can access both the key-value pairs and individual elements by their index, not just by key.

  4. 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

  1. Creating a SortedList:

    • A SortedList<int, string> is created to store integer keys and string values.

    • SortedList<int, string> sortedList = new SortedList<int, string>();

  2. Adding Elements:

    • The Add method is used to insert key-value pairs.

    • sortedList.Add(3, "Three");

  3. Displaying the SortedList:

    • A foreach loop is used to display all elements, and the list will automatically be sorted by key.
  4. Accessing by Key:

    • The value associated with a specific key is accessed directly using sortedList[key].
  5. Modifying Values:

    • You can modify the value for a given key using the indexer (sortedList[key] = newValue).
  6. Removing Elements:

    • The Remove method is used to delete a key-value pair by key.

    • sortedList.Remove(1);

  7. Checking for Key Existence:

    • The ContainsKey method checks if a specific key exists in the list.
  8. Accessing by Index:

    • The ElementAt(index) method is used to get the element at a particular index in the sorted list.

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>

  1. Ordered Collection: When you need to maintain a collection of items that are always sorted by key.

  2. Fast Lookup: If you need to look up values by key in an ordered collection with a small overhead on insertions and deletions.

  3. Efficient for Smaller Collections: While SortedList is efficient in terms of sorting and lookups, it may not be as efficient as Dictionary for larger collections because of the overhead required to maintain sorting.


J
jytiul1y ago

shad+12345@hashnode.com