A Comprehensive Guide to Non-Generic and Generic Collections
This guide covers both the older non-generic collections and the modern generic collections in C#, with detailed explanations, examples, and comparisons.
Non-generic collections store elements as object types, which means they can store any type of data but require casting when retrieving items. They are found in the System.Collections namespace.
Dynamic array that can grow and shrink automatically.
using System.Collections;
ArrayList list = new ArrayList();
list.Add("Hello");
list.Add(42);
list.Add(DateTime.Now);
// Must cast when retrieving
string first = (string)list[0];
int second = (int)list[1];
// This will compile but cause runtime error
// int invalid = (int)list[0];
Collection of key-value pairs organized by hash code.
Hashtable table = new Hashtable();
table.Add("key1", "value1");
table.Add(123, "value2");
table.Add(45.6, "value3");
// Retrieving values
string val1 = (string)table["key1"];
string val2 = (string)table[123];
// Iterating through hashtable
foreach (DictionaryEntry entry in table) {
Console.WriteLine($"{entry.Key}: {entry.Value}");
}
First-In-First-Out (FIFO) and Last-In-First-Out (LIFO) collections.
// Queue example
Queue queue = new Queue();
queue.Enqueue("first");
queue.Enqueue("second");
queue.Enqueue(123);
string firstItem = (string)queue.Dequeue();
// Stack example
Stack stack = new Stack();
stack.Push("bottom");
stack.Push("middle");
stack.Push(456);
string topItem = (string)stack.Pop();
Generic collections are type-safe collections that specify the type of elements they contain. They are found in the System.Collections.Generic namespace and provide better performance and type safety.
Strongly typed list of objects that can be accessed by index.
using System.Collections.Generic;
List stringList = new List();
stringList.Add("first");
stringList.Add("second");
// stringList.Add(123); // Compile error!
string first = stringList[0]; // No casting needed
List intList = new List { 1, 2, 3, 4, 5 };
int sum = 0;
foreach (int num in intList) {
sum += num; // No unboxing overhead
}
Collection of key-value pairs with fast lookups by key.
Dictionary ages = new Dictionary();
ages.Add("John", 25);
ages.Add("Jane", 30);
// ages.Add(123, 45); // Compile error - wrong key type
int johnAge = ages["John"]; // No casting needed
// Iterating with type safety
foreach (KeyValuePair entry in ages) {
Console.WriteLine($"{entry.Key} is {entry.Value} years old");
}
Generic versions of FIFO and LIFO collections.
// Generic Queue
Queue stringQueue = new Queue();
stringQueue.Enqueue("first");
stringQueue.Enqueue("second");
// stringQueue.Enqueue(123); // Compile error
string first = stringQueue.Dequeue(); // No casting
// Generic Stack
Stack intStack = new Stack();
intStack.Push(1);
intStack.Push(2);
// intStack.Push("three"); // Compile error
int top = intStack.Pop(); // No casting
Collections that store unique elements with high-performance set operations.
HashSet set1 = new HashSet { 1, 2, 3, 4, 5 };
HashSet set2 = new HashSet { 4, 5, 6, 7, 8 };
// Set operations
set1.UnionWith(set2); // set1 becomes {1,2,3,4,5,6,7,8}
set1.IntersectWith(set2); // set1 becomes {4,5}
set1.ExceptWith(set2); // set1 becomes {1,2,3}
// SortedSet maintains order
SortedSet sortedSet = new SortedSet { "zebra", "apple", "banana" };
// sortedSet is { "apple", "banana", "zebra" }
| Collection Type | Namespace | Type Safety | Performance | When to Use |
|---|---|---|---|---|
| Non-Generic ArrayList | System.Collections | ❌ No | ❌ Slow (boxing) | Legacy code, mixed types |
| Generic List<T> | System.Collections.Generic | ✅ Yes | ✅ Fast | Most cases needing a list |
| Non-Generic Hashtable | System.Collections | ❌ No | ❌ Slow (boxing) | Legacy code, mixed types |
| Generic Dictionary<TKey, TValue> | System.Collections.Generic | ✅ Yes | ✅ Fast | Key-value lookups |
| Non-Generic Queue/Stack | System.Collections | ❌ No | ❌ Slow (boxing) | Legacy code |
| Generic Queue<T>/Stack<T> | System.Collections.Generic | ✅ Yes | ✅ Fast | FIFO/LIFO collections |
Recommendation: For new development, always prefer generic collections over non-generic ones for better performance, type safety, and code clarity.
.NET provides several specialized collections for specific scenarios:
// ObservableCollection - notifies when changes occur
ObservableCollection observable = new ObservableCollection();
observable.CollectionChanged += (s, e) => {
Console.WriteLine("Collection changed!");
};
// Concurrent Collections - thread-safe
ConcurrentDictionary concurrentDict = new ConcurrentDictionary();
ConcurrentQueue concurrentQueue = new ConcurrentQueue();
ConcurrentStack concurrentStack = new ConcurrentStack();
// ReadOnlyCollection - provides a read-only wrapper
List originalList = new List { 1, 2, 3 };
ReadOnlyCollection readOnly = new ReadOnlyCollection(originalList);
// readOnly.Add(4); // Compile error - collection is read-only