.NET ProgrammingUnit 411 min read

Exception Handling & Collections in .NET: Errors, Lists, Dictionaries

Unit 4 of .NET Programming covers structured error handling (try-catch-finally) and .NET’s core collections (ArrayList, List<T>, Dictionary<TKey,TValue>, Queue<T>, Stack<T>, HashSet<T>), including their operations, performance trade-offs, and real-world use in data processing, caching, and transaction logging.

TAKEAWAYS:

  • Exception handling uses try-catch-finally to gracefully manage runtime errors (e.g., NullReferenceException, DivideByZeroException) and log them for debugging.
  • .NET collections are optimized for different access patterns: List<T> for indexed access, Dictionary<TKey,TValue> for key-value lookups, Queue<T> for FIFO operations, and Stack<T> for LIFO.
  • Generic collections (List<T>, Dictionary<TKey,TValue>) enforce type safety at compile time, improving performance and reducing runtime errors.
  • LINQ with collections enables powerful querying (e.g., Where(), OrderBy()) directly on collection objects.
  • Thread-safe collections (ConcurrentQueue<T>, BlockingCollection<T>) are critical for multi-threaded applications to avoid race conditions.
  • Memory vs. performance trade-offs dictate collection choice: arrays are fastest for fixed-size data, while List<T> dynamically resizes but has overhead.

Exception Handling: Structured Error Management

Why Handle Exceptions?

Unchecked errors (e.g., file not found, invalid user input) crash applications. Exception handling isolates errors, logs them, and allows graceful recovery. In .NET, exceptions are objects derived from System.Exception.

Key Components

flowchart TD
    A["try"] -->|"Error occurs"| B["catch (ExceptionType e)"]
    A -->|"Always executes"| C["finally"]
    B --> D["Log error\nRecover or rethrow"]
    C --> D

Worked Example: File Read with Validation

try
{
    string[] lines = File.ReadAllLines("data.txt");
    int sum = lines.Sum(line => int.Parse(line)); // May throw FormatException
    Console.WriteLine($"Sum: {sum}");
}
catch (FileNotFoundException ex)
{
    Console.WriteLine($"File missing: {ex.Message}");
    // Retry or notify admin
}
catch (FormatException ex)
{
    Console.WriteLine($"Invalid data format: {ex.Message}");
}
finally
{
    Console.WriteLine("Operation attempted.");
}

Trace Table:

Step lines Exception Thrown Output
1 null FileNotFoundException "File missing: data.txt"
2 ["10", "20"] None "Sum: 30"
3 ["abc"] FormatException "Invalid data format: abc"

Common Exceptions in .NET

Exception Type Cause Example
NullReferenceException Accessing a null object string.Length on null
IndexOutOfRangeException Array/list index invalid list[99] on 10-item list
InvalidOperationException Invalid API usage stack.Pop() on empty stack
ArgumentNullException Passing null to a method string.IsNullOrEmpty(null)

Best Practices

  • Catch specific exceptions (avoid catch (Exception) unless logging).
  • Use finally for cleanup (e.g., closing files, releasing locks).
  • Log exceptions with ex.ToString() or Serilog.
  • Avoid empty catch blocks—at least log or rethrow.

Collections: Choosing the Right Data Structure

1. Arrays vs. Lists

Arrays are fixed-size, zero-based, and faster for access but inflexible. List<T> dynamically resizes (default capacity: 4, grows by 2x).


Performance Comparison:

Operation Array (int[]) List<T> (List<int>)
Access by index O(1) O(1)
Insert at end O(1)* O(1)
Insert at start O(n) O(n)
Remove at end O(1) O(1)
Memory overhead Low High (object headers)

*If no resize needed.

2. Dictionaries: Key-Value Lookups

Dictionary<TKey,TValue> uses a hash table for O(1) average-case lookups. Collisions are resolved via chaining (linked lists).


Worked Example: Caching User Sessions

var userSessions = new Dictionary<string, DateTime>();
userSessions["user123"] = DateTime.Now; // Hash: "user123" → Bucket 0
userSessions["admin"] = DateTime.Now.AddHours(-1); // Hash: "admin" → Bucket 1

Trace: Adding and Looking Up

Step userSessions Action Output/State
Initial {} Add("user123", now) Bucket 0: ["user123" → now]
After collision Bucket 1: ["admin" → oldTime] Add("admin", now) Bucket 1: ["admin" → now]
Lookup "user123" Bucket 0: ["user123" → now] userSessions["user123"] Returns now (O(1))

3. Queues and Stacks: FIFO vs. LIFO

  • Queue<T>: First-In-First-Out (e.g., task scheduling, breadth-first search).
  • Stack<T>: Last-In-First-Out (e.g., undo operations, expression evaluation).

Worked Example: Pathao Order Queue

var orderQueue = new Queue<string>();
orderQueue.Enqueue("Order123"); // FIFO: first to be processed
orderQueue.Enqueue("Order124");
Console.WriteLine(orderQueue.Dequeue()); // "Order123"

Trace:

Step orderQueue Operation Output
1 ["Order123"] Enqueue("Order124") ["Order123", "Order124"]
2 ["Order124"] Dequeue() "Order123"

4. HashSet<T>: Unique Elements

HashSet<T> ensures uniqueness via hashing. Useful for deduplication (e.g., removing duplicate emails).

var uniqueEmails = new HashSet<string> { "a@b.com", "c@d.com" };
uniqueEmails.Add("a@b.com"); // Ignored (already exists)
Console.WriteLine(uniqueEmails.Count); // 2

5. Thread-Safe Collections

For multi-threaded apps, use:

  • ConcurrentQueue<T>: Thread-safe queue.
  • BlockingCollection<T>: Blocks when empty/full (e.g., producer-consumer patterns).
var safeQueue = new ConcurrentQueue<int>();
safeQueue.Enqueue(1); // Thread-safe

In the Real World

  1. eSewa (Nepal):

    • Collection Used: Dictionary<string, Transaction> to map transaction IDs to user data.
    • Why? O(1) lookup for verifying payments (e.g., transactions["TXN123"].Status).
    • Exception Handling: Catches PaymentFailedException to retry or notify users.
  2. Khalti (Nepal):

    • Collection Used: Queue<PaymentRequest> for processing payments in FIFO order.
    • Why? Ensures fairness and prevents starvation (e.g., paymentQueue.Dequeue()).
  3. Daraz (Nepal):

    • Collection Used: List<Order> for order management + HashSet<string> to track unique SKUs.
    • Why? HashSet avoids duplicate inventory counts; List maintains order for processing.
  4. NTC (Nepal Telecom):

    • Exception Handling: Logs NetworkUnavailableException when SIM registration fails, then retries.
  5. Bank Loan Calculations (Global):

    • Collection Used: Dictionary<int, Loan> where int is account number.
    • Worked Example: Calculate monthly interest for account 1001:
      var loans = new Dictionary<int, Loan> { { 1001, new Loan(100000, 0.05) } };
      try
      {
          double monthlyInterest = loans[1001].CalculateMonthlyInterest();
          Console.WriteLine(monthlyInterest); // 416.67
      }
      catch (KeyNotFoundException)
      {
          Console.WriteLine("Account not found.");
      }
      

LINQ with Collections: Querying Data

LINQ (Language Integrated Query) extends collections with SQL-like operations.

var numbers = new List<int> { 1, 5, 3, 8, 2 };
var evens = numbers.Where(n => n % 2 == 0).OrderBy(n => n);
Console.WriteLine(string.Join(", ", evens)); // "2, 8"

Common LINQ Methods:

Method Purpose Example
Where() Filter elements .Where(x => x > 5)
Select() Project to new type .Select(x => x * 2)
OrderBy() Sort ascending .OrderBy(x => x.Name)
GroupBy() Group by key .GroupBy(x => x.Category)
FirstOrDefault() Get first or default .FirstOrDefault()

Performance Considerations

Collection Best For Worst For Time Complexity (Avg)
List<T> Frequent access by index Frequent insertions O(1) access, O(n) insert at start
Dictionary<TKey,TValue> Key-value lookups Many collisions O(1) lookup, O(n) resize
Queue<T> FIFO operations Random access O(1) enqueue/dequeue
Stack<T> LIFO operations Sequential access O(1) push/pop
HashSet<T> Uniqueness checks Order matters O(1) add/contains

Exam Tip

  1. Exception Handling:

    • Must-know: try-catch-finally syntax, using for IDisposable (e.g., FileStream).
    • Common pitfalls: Swallowing exceptions (catch (Exception) without logging), not cleaning up in finally.
    • Exam question type: Given a code snippet with potential errors, identify the exception thrown and fix it.
  2. Collections:

    • Must-know: When to use List<T> vs. Dictionary<TKey,TValue> vs. Queue<T>.
    • Performance trade-offs: Memorize O(1) vs. O(n) operations for each collection.
    • Exam question type:
      • "Which collection would you use for [scenario] and why?"
      • "Trace the state of a Dictionary after a series of Add/Remove operations."
      • "Rewrite this array-based solution using List<T>."
  3. LINQ:

    • Must-know: Where(), Select(), OrderBy(), GroupBy().
    • Exam question type: Given a collection, write a LINQ query to filter/sort/group data.
  4. Real-world scenarios:

    • Expect questions like:
      • "How would you implement a user session cache using Dictionary?"
      • "Design a thread-safe order processing system using BlockingCollection."

Summary Checklist

Before the exam, verify you can:

  • Write try-catch-finally blocks for common exceptions.
  • Choose the right collection for a given scenario (e.g., Queue for task scheduling).
  • Trace the state of a collection after operations (e.g., Dictionary after collisions).
  • Use LINQ to query collections (e.g., filter and sort).
  • Explain the trade-offs between arrays and List<T>.
  • Describe how Dictionary handles collisions (hashing + chaining).

Based on the TU BIM syllabus for .NET Programming (IT275), unit 4.

Discussion

Loading…