Debugging C# Code: From "Why isn't it working?" to "I know where the error is"
\n\nEvery C# developer sooner or later encounters a situation where the code compiles, runs, but does something completely unexpected. Or it crashes with an error that cannot be reproduced in a test environment. In such moments, debugging C# turns from a routine task into a true art.
\n\nIn this article, we will cover techniques and tools that will help you not just find a bug, but do it quickly and efficiently. We won't limit ourselves to the standard F5 — we'll talk about advanced breakpoints, conditional expressions, memory analysis, and even working with dumps in WinDbg. Let's go!
\n\nBasic but Powerful C# Debugging Techniques
\n\nBefore moving on to exotic tools, let's make sure you're getting the most out of Visual Studio. Many developers only use F9 (set a breakpoint) and F10 (step over), which is a shame.
Conditional Breakpoints
\n\nImagine: you have a loop with 1000 iterations, and the bug only occurs on the 567th. Set a breakpoint and press F5 567 times? No. Use conditional breakpoints.
\n\nfor (int i = 0; i < 1000; i++)\n{\n // Some logic\n ProcessItem(items[i]);\n}\n\nRight-click the red breakpoint circle → select "Conditions" → enter i == 567. When the counter reaches the desired value, the debugger will stop automatically. This saves hours of your life.
As a condition, you can use not only a comparison but any boolean expression in C#. For example: items[i].IsValid == false && items[i].Id > 100.
Breakpoints with Actions (Tracepoints)
\n\nSometimes you don't need to stop execution, but simply log something at a specific moment. For this, there are breakpoints with actions. Set a breakpoint, right-click → "Actions" → enter a message like Method called with parameter: {parameter}.
This works like Console.WriteLine, but without changing the code. You can output variable values directly to the Output window without interrupting the application.
Attach to Process
\n\nEver had a bug that only reproduces in production, but not locally? Use Debug → Attach to Process. You can connect to an already running process (e.g., IIS, Windows Service, or a desktop application).
Important note: make sure you have PDB files (symbols) for the version of the assembly that is running. Otherwise, instead of variable names, you'll see Variable1 and Variable2.
Advanced Techniques: Diagnostics Without Stopping
\n\nNot all bugs can be caught in the debugger. Sometimes stopping execution changes the program's behavior (e.g., due to timing in multithreaded code). For such cases, there are other C# debugging techniques.
\n\nLogging with DiagnosticSource and Serilog
\n\nThe built-in System.Diagnostics.Trace is good, but for serious projects, it's better to use structured logging. Serilog is one of the most popular loggers for .NET.
using Serilog;\n\nLog.Logger = new LoggerConfiguration()\n .WriteTo.Console()\n .WriteTo.File("log.txt", rollingInterval: RollingInterval.Day)\n .CreateLogger();\n\ntry\n{\n var result = SomeRiskyOperation();\n Log.Information("Operation completed successfully. Result: {Result}", result);\n}\ncatch (Exception ex)\n{\n Log.Error(ex, "Error executing operation");\n}\n\nStructured logs allow you to search for errors not by text, but by fields. For example, you can find all logs with OrderId = 12345 from the last hour. This is more powerful than grepping a text file.