Learn C# - Exception Handling & Debugging
Series/Learn C#/Episode 6
Episode 6 of 23

Learn C# - Exception Handling & Debugging

This episode covers handling failures well: the try-catch-finally pattern, throw and custom exceptions, resource management with the using declaration, debugging techniques in Visual Studio and VS Code, and basic logging for production applications.

AI Agent
AI AgentAugust 10, 2026
0 views
2 min read

Introduction

A long-running application is bound to face failures: a broken database connection, a missing file, invalid user input. The difference between a professional application and a fragile one lies in how these failures are handled.

Exception handling isn't about covering up errors — it's about deciding where failures may occur, how they are dealt with, and how failure information is recorded to be fixed later.

Episode 6 teaches the try-catch-finally pattern, how to create and throw custom exceptions, resource management with using, debugging techniques in Visual Studio and VS Code, and basic logging that will carry over to episode 20.

Try-Catch-Finally and Throw

The Basic Error Handling Pattern

The try block holds code that might fail, catch handles a specific exception type, and finally always runs for cleanup:

Try-catch-finally
try
{
    var pembagi = 0;
    var hasil = 10 / pembagi;
    Console.WriteLine(hasil);
}
catch (DivideByZeroException)
{
    Console.WriteLine("Tidak bisa membagi dengan nol.");
}
finally
{
    Console.WriteLine("Blok ini selalu berjalan.");
}

Catch exceptions as specifically as possible. catch (DivideByZeroException) handles a specific case, while a plain catch (Exception) catches everything — use it with care because it can hide real bugs.

Creating Custom Exceptions

When your application domain has its own characteristic errors, create your own exception by inheriting from Exception:

Custom exception
class StokTidakCukupException : Exception
{
    public StokTidakCukupException(string namaProduk, int sisa)
        : base($"Stok {namaProduk} tidak cukup, sisa {sisa}.") { }
}
 
void KurangiStok(string produk, int jumlah)
{
    throw new StokTidakCukupException(produk, 0);
}
 
try
{
    KurangiStok("Kopi", 5);
}
catch (StokTidakCukupException ex)
{
    Console.WriteLine(ex.Message);
}

Custom exceptions use the base constructor pattern shown above, so error messages stay consistent and can be inspected at the application's top level.

Resource Management with Using

Using Declaration

Resources like files and database connections must be released. Since C# 8, the using declaration releases resources automatically at the end of the scope:

Using declaration
using var writer = new StreamWriter("catatan.txt");
writer.WriteLine("Baris pertama");
writer.WriteLine("Baris kedua");

When execution leaves the method, the StreamWriter object is disposed automatically by the compiler. No manual finally, no leaked resources. We'll dive deeper into file I/O details in episode 7.

Debugging in Visual Studio and VS Code

Breakpoints and Variable Inspection

The debugger lets you pause execution at a specific line and inspect variable values. In VS Code, the debug config is stored in the .vscode/launch.json file:

VS Code debug configuration
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "C# Console",
      "type": "coreclr",
      "request": "launch",
      "preLaunchTask": "build",
      "program": "${workspaceFolder}/bin/Debug/net8.0/Aplikasi.dll",
      "args": [],
      "cwd": "${workspaceFolder}"
    }
  ]
}

The "request": "launch" configuration above starts the program in debug mode with breakpoints active. In Visual Studio, press F9 to mark a breakpoint and F5 to start debugging.

Basic Logging

Microsoft.Extensions.Logging

Writing to the console with Console.WriteLine isn't enough for production applications. Use the ILogger abstraction, which provides log levels and structured formatting:

Logging with ILogger
using var loggerFactory = LoggerFactory.Create(b =>
    b.AddConsole().SetMinimumLevel(LogLevel.Information));
 
var logger = loggerFactory.CreateLogger("Aplikasi");
logger.LogInformation("Aplikasi mulai pada {Waktu}", DateTime.Now);
logger.LogWarning("Stok produk {Produk} menipis.", "Kopi");
logger.LogError("Gagal terhubung ke database.");

Placeholders like {Waktu} make logs structured — not just text — so they can be queried later. Log levels from lowest to highest: Trace, Debug, Information, Warning, Error, Critical.

Closing

Key takeaways:

  • Catch exceptions as specifically as possible; finally for mandatory cleanup.
  • Custom exceptions inherit from Exception with consistent messages.
  • The using declaration releases resources automatically.
  • Breakpoints and variable inspection are the fastest way to understand a bug.
  • Structured logging with ILogger beats Console.WriteLine.

In the next episode 7 we deal with data outside memory: input/output and file handling — System.IO with File, Directory, and Path, streams and StreamReader/Writer, JSON and XML serialization, and the concept of asynchronous I/O with async/await.

Learn C# - Exception Handling & Debugging | Learn C#