C#  

Collections in C#: From Arrays to Advanced Generics for Professional Developers

C#’s strong collection types enable developers to store, organize, and manipulate data, crucial for real-world applications like HR systems, payroll solutions, or project management tools. This article walks through the most essential built-in collections, explaining their use cases, code syntax, and practical scenarios for beginners through senior engineers.

1. C# Arrays

Arrays are fixed-size, strongly typed collections used for storing simple, indexed lists of elements.

Example: Employee Array

public class Employee

{

  public int Id {
    get;
    set;
  }

  public string Name {
    get;
    set;
  }

}

// Declare and initialize

Employee[] employees = new Employee[3];

employees[0] = new Employee {
  Id = 1, Name = "Kiran"
};

employees[1] = new Employee {
  Id = 2, Name = "Kumar"
};

employees[2] = new Employee {
  Id = 3, Name = "Mark"
};

// Access by index

Console.WriteLine(employees[1].Name); // Output: Kiran

// Iteration

foreach(var emp in employees)

Console.WriteLine($"{emp.Id}: {emp.Name}");

Key points

  • Fixed size; cannot dynamically add/remove items after creation.
  • Fast access via index.
  • Use when the number of employees is known and unchanging.

2. Non-Generic Collections

ArrayList

  • Stores objects (any type), resizes dynamically, but is not type-safe.
  • Now largely obsolete—prefer generics.
using System.Collections;

ArrayList staff = new ArrayList();

staff.Add("Kiran"); // string

staff.Add(100); // int

staff.Add(new Employee {
  Id = 1, Name = "Kumar"
}); // Employee object

foreach(var s in staff)

Console.WriteLine(s); // May cause runtime errors if type is unexpected!

Drawbacks: No compile-time type checking; risk of invalid casts.

3. Generic Collections

Use these for all new code. Part of System.Collections.Generic.

List<T>

Resizable, type-safe, index-accessed collection.

 

List < Employee > employeeList = new List < Employee >

  {

    new Employee {
      Id = 5, Name = "Derek"
    },

    new Employee {
      Id = 6, Name = "Kiran"
    }

  };

employeeList.Add(new Employee {
  Id = 7, Name = "Mark"
});

foreach(var e in employeeList)

Console.WriteLine($"{e.Id}: {e.Name}");

Dictionary < TKey, TValue >

Dictionary<TKey, TValue>

Key-value pairs for lookups, e.g., by Employee ID.

Dictionary < int, Employee > directory = new Dictionary < int, Employee > ();

directory[101] = new Employee {
  Id = 101, Name = "Kiran"
};

directory.Add(102, new Employee {
  Id = 102, Name = "Kumar"
});

if (directory.ContainsKey(101))

  Console.WriteLine(directory[101].Name); // Output: Kiran

Queue<T>

First-In-First-Out (FIFO) collection. Use for ordered tasks like employee service requests.

Queue < string > helpDesk = new Queue < string > ();

helpDesk.Enqueue("Reset Credentails for Kiran");

helpDesk.Enqueue("Setup email for Kumar");

// Process tasks

while (helpDesk.Count > 0)

  Console.WriteLine("Processing: " + helpDesk.Dequeue());

Stack<T>

Last-In-First-Out (LIFO). Good for undo/redo operations or backtracking user changes.

Stack < string > employeeUndo = new Stack < string > ();

employeeUndo.Push("Added Kiran");
employeeUndo.Push("Edited Kumar");

Console.WriteLine(employeeUndo.Pop()); // Output: Edited Kumar

HashSet<T>

Unordered, unique elements. Great for storing unique employee emails or IDs.

HashSet < string > emails = new HashSet < string > ();

emails.Add("[email protected]");
emails.Add("kumar@ microsoft.com");
emails.Add("mark@ microsoft.com"); // Ignored (duplicate)

LinkedList<T>

Doubly-linked list; efficient for insertions/removals at both ends or in the middle.

LinkedList < string > activities = new LinkedList < string > ();

activities.AddLast("Interview");
activities.AddFirst("Application Received");

SortedList<TKey, TValue> & SortedDictionary<TKey, TValue>

Keep the key-order automatically. Use for sorted employee directories.

SortedList < int, string > sortedById = new SortedList < int, string > ();

sortedById.Add(2, "Kiran");
sortedById.Add(1, "Kumar");

// Now ordered by key: 1, 2

SortedSet<T>

Unique elements, always sorted. Use for unique badge numbers or sorted skill sets.

SortedSet < int > badges = new SortedSet < int > {
  1004,
  1001,
  1003
};

foreach(var badge in badges)

Console.WriteLine(badge); // 1001, 1003, 1004

4. Specialized & Advanced Collections

Concurrent Collections

For multi-threaded applications (rare in basic employee management):

  • ConcurrentDictionary<TKey, TValue>
  • ConcurrentQueue<T>
  • ConcurrentBag<T>

ObservableCollection<T>

For UI data binding (WPF, etc.)—auto-updates UI when collection changes.

ReadOnlyCollection<T>

Provides a read-only wrapper for existing collections when data should not be modified.

5. Choosing the Right Collection

Collection Best For
Array Fixed size, fastest access, simple scenarios
ArrayList Legacy; avoid in new code
List<T> Dynamic size, fast lookups, and most general cases
Dictionary<TKey, TValue> Key-based lookup (e.g., by ID)
Queue<T> FIFO tasks/processes
Stack<T> LIFO/undo-redo/backtracking
HashSet<T> Unique, unordered elements
LinkedList<T> Frequent insertions/removals anywhere
SortedList/SortedSet<T> Unique + sorted, fast ordered access
Concurrent* Thread-safe situations
ObservableCollection<T> UI data-binding

6. Best Practices for Engineers of All Levels

  • Use generic collections (e.g., List<T>, Dictionary<TKey,TValue>) for type safety and performance.
  • Avoid ArrayList and non-generic collections unless maintaining legacy code.
  • Understand each collection’s strengths (lookups, order, uniqueness, sorting).
  • Choose based on use case: fixed size (array), dynamic (List), lookup (Dictionary), etc.
  • Use LINQ for querying and manipulating collections (bonus for advanced users).
  • Favor immutability with ReadOnlyCollection<T> when exposing data publicly.

7. Entry-Level to Senior Learning 

  • Entry: Arrays, basic List<T>, Dictionary<TKey, TValue>
  • Intermediate: Queue<T>, Stack<T>, HashSet<T>, LINQ basics
  • Advanced: LinkedList<T>, Sorted* collections, custom collections, concurrent/immutable collections, LINQ mastery

Conclusion

Mastering collections is foundational for C# development. Use arrays for small, fixed groups; lists and dictionaries for dynamic, type-safe access; and select  stacks, queues, sets, and advanced collections as your needs become more complex program.