Broadly speaking, collection types can fall under six categories. Here is a list of those categories along with respective collection types.
Non-Generic Collections
Non-generic collections in C# are collections that can store objects of any type, as they work with the base System. Object type rather than a specific type. See below a list of those types.
- ArrayList: A dynamic array that automatically resizes itself as elements are added or removed.
- Hashtable: A collection of key/value pairs, organized based on the hash code of the key.
- Queue: A first-in-first-out (FIFO) collection.
- Stack: A last-in-first-out (LIFO) collection.
- SortedList: A collection of key/value pairs that are sorted by the keys.
When to Use Non-Generic Collections?
- When you need to support older codebases that rely on non-generic collections.
- When the types of objects being stored are not known in advance and can be mixed.
Generic Collections
Generic collections in C# are collections that are designed to hold objects of a specific type, specified at the time of the collection’s declaration. They are from System.Collections.Generic namespace. See below a list of those types.
- List: A dynamic array that automatically resizes itself as elements are added or removed.
- Dictionary<TKey, TValue>: A collection of key/value pairs, organized based on the hash code of the key.
- Queue: A first-in-first-out (FIFO) collection.
- Stack: A last-in-first-out (LIFO) collection.
- SortedList<TKey, TValue>: A collection of key/value pairs that are sorted by the keys.
- SortedDictionary<TKey, TValue>: Similar to SortedList<TKey, TValue>, but more optimized for insertions and deletions.
- HashSet: A collection of unique elements that provides high-performance set operations.
- LinkedList: A doubly linked list that allows for efficient insertions and deletions.
Note. Queue and Stack have both generic and non-generic versions.
When to Use Generic Collections?
- When you want type safety and compile-time type checking.
- When you need better performance, especially with value types.
Specialized Collections
Specialized collections in C# are collections that are designed for specific, often more specialized, data storage and retrieval scenarios. These collections are part of the System.Collections.Specialized namespace and offer functionality that is not typically provided by the more general-purpose collections found in the System.Collections or System.Collections.Generic. See below a list of those types:
- NameValueCollection: A collection of associated String keys and String values that can be accessed either by key or by index. Useful for scenarios where you need to store multiple values under a single key.
- StringCollection: A strongly typed collection designed to store a collection of strings.
- StringDictionary: A dictionary specifically for storing string keys and string values.
- BitVector32: A structure that stores Boolean values and small integers in 32 bits of memory. Useful for compact storage of multiple flags and small integers.
- HybridDictionary: A dictionary that starts as a ListDictionary (optimized for small collections) and switches to a Hashtable when it becomes large enough. Provides a balance between performance and memory usage.
- ListDictionary: A dictionary optimized for small collections (usually less than 10 items).
- OrderedDictionary: A dictionary that maintains the order of the elements as they are added. Allows access by both key and index.
Concurrent Collections
Concurrent collections in C# are specialized collections designed for scenarios where multiple threads need to access and modify the collection simultaneously. These collections are part of the
System.Collections.Concurrent namespace and provide thread-safe operations, which significantly simplify concurrent programming by managing synchronization internally.
- ConcurrentBag: An unordered collection of objects that supports fast parallel operations. It’s suitable for scenarios where the order of elements does not matter.
- ConcurrentDictionary<TKey, TValue>: A thread-safe dictionary that allows safe, efficient, concurrent access to its elements. Ideal for scenarios where you need to store key/value pairs accessed or modified by multiple threads.
- ConcurrentQueue: A thread-safe first-in, first-out (FIFO) collection. Suitable for producer-consumer scenarios where multiple threads enqueue and dequeue items.
- ConcurrentStack: A thread-safe last-in, first-out (LIFO) collection. Suitable for scenarios where you need a stack data structure with concurrent access.
- BlockingCollection: A thread-safe, bounded (A bounded collection has a fixed capacity) or unbounded (An unbounded collection can grow indefinitely, limited only by system memory.) collection that supports adding and taking elements, blocking if necessary. It can be used as a wrapper around other concurrent collections to provide bounding and blocking capabilities, making it ideal for producer-consumer scenarios.
Immutable Collections
Immutable collections in C# are collections that cannot be modified after they are created. This immutability provides several advantages, particularly in multi-threaded and functional programming scenarios. Immutable collections are part of the System.Collections.Immutable namespace.
- ImmutableArray: An immutable version of an array.
- ImmutableList: An immutable version of List.
- ImmutableDictionary<TKey, TValue>: An immutable version of the dictionary.
- ImmutableHashSet: An immutable version of HashSet
- ImmutableQueue: An immutable version of Queue
- ImmutableSortedDictionary<TKey, TValue>: An immutable version of SortedDictionary.
- ImmutableSortedSet: An immutable version of SortedSet.
- ImmutableStack: An immutable version of Stack.
LINQ Collections
LINQ (Language Integrated Query) is a powerful feature in C# that allows you to perform queries on collections in a concise and readable manner. LINQ provides a set of querying methods that operate on collections implementing the IEnumerable<T> or IQueryable<T> interfaces. These interfaces are typically found in the System.Linq namespace.
- IEnumerable: Represents a collection of objects that can be enumerated. LINQ methods that operate on IEnumerable<T> are executed in memory, and they are called LINQ to Objects.
- IQueryable: Represents a collection of objects that can be queried. LINQ methods that operate on IQueryable<T> can be translated to other query languages, such as SQL, and executed in a different context, like a database. This is called LINQ to SQL or LINQ to Entities (for Entity Framework).
Conclusion
C# offers a robust set of collection types, catering to diverse programming needs. Generic collections are generally preferred for their type safety and compile-time checking, while non-generic collections are used for compatibility with older codebases or when types aren’t known at compile time. Specialized collections provide optimized performance for specific scenarios, and concurrent collections simplify multi-threaded development with thread-safe operations. Immutable collections are ideal for maintaining data integrity in multi-threaded environments. Additionally, LINQ enhances collection manipulation with powerful, expressive querying capabilities, resulting in concise and maintainable code.