Compile errors versus runtime exceptions
C# problems arrive at two very different moments. A compile error is the compiler (Roslyn) refusing to build your program: the syntax is wrong, a name does not exist, or the types do not fit. Nothing runs at all. A runtime exception happens while the program is running: the code was valid, but the data was not what you expected — a null, a missing key, text that is not a number. Knowing which one you are looking at tells you where to look.
Compile error
- Reported by
dotnet build/dotnet runbefore anything executes - Format:
File.cs(line,col): error CS0103: … - Always happens, every time, on every machine
- The editor underlines it in red as you type
- Fix the code the message points at
Runtime exception
- Thrown while the program runs; everything before it already happened
- Format:
Unhandled exception. System.XxxException: message+ stack trace - May depend on input, data, timing or environment
- Invisible until that line runs with that data
- Fix the data flow that led there, or handle the case
Anatomy of a compiler error
Program.cs(6,19): error CS0103: The name 'grade' does not exist in the current context
Program.cs(4,12): warning CS0219: The variable 'grade' is assigned but its value is never used
The build failed. Fix the build errors and run again.File, (line,column), severity, the CS code, the message. Warnings do not stop the build; errors do.
- Fix the first error first. One missing brace can produce twenty follow-on errors that vanish once it is fixed.
- Search the code. Every CS number has a page on Microsoft Learn with examples — searching "CS0266" is faster than searching the message.
- Read warnings too. The warning above is a clue to the error: the variable exists, just not where it is used.
Reading a .NET stack trace
This program calls PlaceOrder, which calls UnitPrice, which calls Divide. The second quantity is 0, and here is what .NET printed:
PlaceOrder(new[] { 2, 0, 1 });
static void PlaceOrder(int[] quantities)
{
foreach (int q in quantities)
Console.WriteLine(UnitPrice(100, q));
}
static int UnitPrice(int total, int qty)
{
return Divide(total, qty);
}
static int Divide(int a, int b) => a / b;50
Unhandled exception. System.DivideByZeroException: Attempted to divide by zero.
at Program.<<Main>$>g__Divide|0_2(Int32 a, Int32 b) in Program.cs:line 14
at Program.<<Main>$>g__UnitPrice|0_1(Int32 total, Int32 qty) in Program.cs:line 11
at Program.<<Main>$>g__PlaceOrder|0_0(Int32[] quantities) in Program.cs:line 6
at Program.<Main>$(String[] args) in Program.cs:line 1Real output. The first line printed (50) shows the first order worked; the crash came on the second.
- 11. First line: what went wrong
The exception type and message:
DivideByZeroException: Attempted to divide by zero. - 22. Bottom line: where the story starts
<Main>$is your top-level statements, line 1. Reading upwards gives the chain of calls that led to the crash: Main → PlaceOrder → UnitPrice → Divide. - 33. Top "at" line: where it was thrown
Line 14, inside
Divide. Names like<<Main>$>g__Divide|0_2are how the compiler names local functions; read them as "Divide". - 44. Find the first frame that is YOUR mistake
Divide did what it was told. The real question is why
qtywas 0 — so the fix probably belongs inUnitPriceorPlaceOrder, not in Divide. - 55. Inner exceptions
A line starting
--->introduces an inner exception: the original cause that was wrapped by another. The innermost one is usually the one to fix.
You can also catch an exception and inspect it in code: its type, Message, and InnerException. This is what logging frameworks do for you.
try
{
LoadConfig("port=abc");
}
catch (Exception ex)
{
Console.WriteLine($"{ex.GetType().Name}: {ex.Message}");
Console.WriteLine($"caused by {ex.InnerException?.GetType().Name}: {ex.InnerException?.Message}");
}
static int LoadConfig(string line)
{
string value = line.Split('=')[1];
try
{
return int.Parse(value);
}
catch (FormatException fe)
{
throw new InvalidOperationException($"bad config line '{line}'", fe); // keep the cause
}
}InvalidOperationException: bad config line 'port=abc'
caused by FormatException: The input string 'abc' was not in a correct format.Remove the fe argument from the throw and run it again. What does the second line print now, and why is that worse when you are debugging production logs?
