Introduction to Generic Collections
Vaibhav • September 10, 2025
Arrays are powerful and efficient, but they come with limitations. Arrays have a fixed size - once created, you cannot grow or shrink them. In many real-world applications, we need more flexibility. For example, think of managing a to-do list: new tasks can be added or removed anytime, and it’s hard to predict the exact number upfront. This is where collections come in.
What Are Collections?
A collection is a data structure that can hold multiple items, just like an array, but with additional functionality. Unlike arrays, collections are usually dynamic - they can expand or shrink as needed. They also provide powerful methods for searching, sorting, adding, and removing elements.
In C#, collections are available under the System.Collections
and
System.Collections.Generic
namespaces. Early versions of C# provided
non-generic collections (like ArrayList
), but these had drawbacks
around type safety. Modern C# uses generic collections, which are both safer and faster.
Collections are essentially "containers" for objects, designed to make data management easier and more flexible than raw arrays. The .NET Framework provides a wide variety of them, each optimized for different scenarios.
The Problem with Non-Generic Collections
Before generics, developers often used ArrayList
to store objects of any type.
The issue? Every element is treated as an object
. This means when you add a
value type (like an int
), it must be boxed (converted to an
object), and when retrieving it, you must unbox (cast it back). This not only reduces
performance but also increases the chance of runtime errors if the cast is wrong.
// Old way: using ArrayList (non-generic)
ArrayList list = new ArrayList();
list.Add(1); // Boxing occurs
list.Add("Hello"); // Mixed types allowed
// Retrieving
int num = (int)list[0]; // Unboxing required
string text = (string)list[1];
Notice how you can store int
and string
in the
same collection. This might look flexible, but it also means errors can easily occur if you expect one type and
accidentally retrieve another.
Boxing/unboxing is an expensive operation. It allocates additional memory on the heap and impacts performance when done repeatedly in large-scale applications.
Enter Generics - Type-Safe Collections
Generics were introduced in C# 2.0 to solve the problems of type safety and performance. A generic collection allows you to specify the type of elements it stores. This prevents mixing types accidentally and removes the need for boxing/unboxing.
// Modern way: using List<T> (generic)
List<int> numbers = new List<int>();
numbers.Add(1);
numbers.Add(2);
numbers.Add(3);
int first = numbers[0]; // No unboxing needed
Console.WriteLine(first); // Output: 1
Here, List<int>
can only store integers. The compiler enforces this at
compile time, meaning you cannot accidentally add a string. This ensures both safety and
performance.
Generic collections like List<T>
,
Dictionary<K,V>
, and HashSet<T>
are built on highly optimized data structures, making them suitable even for enterprise-scale applications.
Benefits of Generic Collections
- Type safety: Prevents mixing incompatible types.
- Performance: No boxing/unboxing overhead for value types.
- Readability: Clearer intent - you know exactly what type the collection holds.
- Reusability: Generic code can be reused for multiple data types.
Collections vs Arrays
Both arrays and collections are essential tools, but they serve different purposes:
- Arrays: Fixed size, faster for small, predictable data sets, and directly stored in contiguous memory.
- Collections: Flexible, can grow/shrink, provide rich APIs for manipulation, and are easier to use in dynamic scenarios.
For example, if you are storing the 12 months of a year, an array is perfect because the size never changes. But if you are storing user inputs in a chat application, a collection is more appropriate since the number of messages is unpredictable.
Use arrays for small, fixed-size data when performance is critical. For anything dynamic or
large-scale, prefer generic collections like List<T>
or
Dictionary<K,V>
.
Summary
Arrays gave us our first taste of working with collections of data, but they come with fixed limitations.
Generic collections overcome those limitations by providing type-safe, efficient, and flexible
alternatives. They eliminate boxing/unboxing issues, prevent runtime casting errors, and offer a rich set of
features tailored for different scenarios. As we move forward, we’ll explore specific collection types like
List<T>
, Dictionary<K,V>
, and
HashSet<T>
, learning how each shines in its own use cases.