.NET Collections

Introduction

The .NET Framework and .NET Core provide a rich set of classes for working with collections of objects. Collections are fundamental to almost all programming tasks, allowing you to store, retrieve, and manipulate groups of data efficiently. The primary namespace for collections is System.Collections and its generic counterpart System.Collections.Generic.

Generic collections offer type safety, improving code robustness and often performance by avoiding boxing and unboxing operations required by non-generic collections.

Common Interfaces

Several interfaces define the behavior of collections. Understanding these interfaces helps in choosing the right collection type and writing flexible code.

  • IEnumerable<T>: The base interface for all generic collections, enabling iteration using foreach.
  • ICollection<T>: Extends IEnumerable<T>, adding methods for counting elements and adding/removing them.
  • IList<T>: Extends ICollection<T>, providing index-based access to elements (like arrays).
  • IDictionary<TKey, TValue>: Represents a collection of key/value pairs.
  • IReadOnlyCollection<T>, IReadOnlyList<T>, IReadOnlyDictionary<TKey, TValue>: Interfaces for read-only collections.

Non-generic collections implement interfaces like IEnumerable, ICollection, IList, and IDictionary.

Generic Collections

Generic collections reside in the System.Collections.Generic namespace and provide type safety.

List<T>

A dynamically sized array that can store elements of a specific type T. It offers excellent performance for adding elements at the end and accessing elements by index.

Example:


using System.Collections.Generic;

List<string> names = new List<string>();
names.Add("Alice");
names.Add("Bob");
names.Add("Charlie");

Console.WriteLine(names[0]); // Output: Alice
Console.WriteLine(names.Count); // Output: 3

foreach (string name in names)
{
    Console.WriteLine(name);
}
                        

Dictionary<TKey, TValue>

A collection of unique keys and their associated values. It uses a hash table for efficient lookups, insertions, and deletions based on the key.

Example:


using System.Collections.Generic;

Dictionary<int, string> studentGrades = new Dictionary<int, string>();
studentGrades.Add(101, "A");
studentGrades.Add(102, "B+");
studentGrades.Add(103, "A-");

Console.WriteLine(studentGrades[102]); // Output: B+

if (studentGrades.ContainsKey(104))
{
    Console.WriteLine("Student 104 exists.");
}
else
{
    Console.WriteLine("Student 104 not found.");
}
                        

HashSet<T>

A collection that stores unique elements of a specific type T. It does not maintain any particular order and provides very fast Add, Remove, and Contains operations.

Example:


using System.Collections.Generic;

HashSet<int> uniqueNumbers = new HashSet<int>();
uniqueNumbers.Add(1);
uniqueNumbers.Add(2);
uniqueNumbers.Add(1); // Duplicate, will be ignored

Console.WriteLine(uniqueNumbers.Count); // Output: 2
Console.WriteLine(uniqueNumbers.Contains(2)); // Output: True
                        

Queue<T>

Represents a first-in, first-out (FIFO) collection of objects. Elements are added to the end and removed from the beginning.

Example:


using System.Collections.Generic;

Queue<string> tasks = new Queue<string>();
tasks.Enqueue("Task 1");
tasks.Enqueue("Task 2");

string currentTask = tasks.Dequeue(); // "Task 1"
Console.WriteLine(currentTask);
                        

Stack<T>

Represents a last-in, first-out (LIFO) collection of objects. Elements are added to the top and removed from the top.

Example:


using System.Collections.Generic;

Stack<int> numbers = new Stack<int>();
numbers.Push(10);
numbers.Push(20);

int topNumber = numbers.Pop(); // 20
Console.WriteLine(topNumber);
                        

SortedList<TKey, TValue>

A generic collection that stores key-value pairs and maintains them in ascending order of keys. It uses a binary search algorithm to retrieve an element.

SortedDictionary<TKey, TValue>

Similar to SortedList<TKey, TValue>, but uses a red-black tree internally, offering better performance for insertions and deletions compared to SortedList, especially with large collections.

ArrayList (Non-generic)

While it exists for backward compatibility, ArrayList is generally discouraged in favor of generic collections. It stores objects and requires boxing/unboxing, leading to potential performance overhead and runtime errors if type casting is incorrect.

Non-generic Collections

Found in the System.Collections namespace, these collections store elements as objects and are less type-safe than their generic counterparts.

  • Hashtable: A key-value pair collection that is not type-safe. Use Dictionary<TKey, TValue> instead.
  • ArrayList: A dynamic array that stores elements as objects. Use List<T> instead.
  • Queue: A non-generic FIFO collection. Use Queue<T> instead.
  • Stack: A non-generic LIFO collection. Use Stack<T> instead.

LINQ to Collections

Language Integrated Query (LINQ) provides powerful, declarative querying capabilities for collections. You can use LINQ methods with generic collections (and non-generic collections via IEnumerable) to filter, project, and aggregate data.

Example:


using System.Linq;
using System.Collections.Generic;

List<int> numbers = new List<int> { 5, 1, 8, 3, 9, 2 };

// Filter for even numbers greater than 3
var evenNumbers = numbers.Where(n => n % 2 == 0 && n > 3);

foreach (int num in evenNumbers)
{
    Console.WriteLine(num); // Output: 8
}

// Sum of all numbers
int sum = numbers.Sum();
Console.WriteLine($"Sum: {sum}"); // Output: Sum: 28
                    

Performance Considerations

  • Generic vs. Non-generic: Always prefer generic collections (e.g., List<T> over ArrayList) for type safety and performance.
  • Lookup Performance: Dictionary<TKey, TValue> and HashSet<T> offer average O(1) complexity for lookups, additions, and removals, making them ideal for searching and membership testing.
  • Ordered Collections: SortedDictionary<TKey, TValue> and SortedList<TKey, TValue> provide ordered storage but have O(log n) complexity for add/remove operations.
  • Sequence Access: List<T> and arrays provide O(1) access by index.
  • Memory Usage: Different collection types have varying memory overheads. For very large datasets, consider specialized collections or data structures if standard ones become inefficient.