C# & .NET (C# 14, .NET 10 LTS) Module 1 – Start here: C#, .NET and your first programs
Compiler errors, stack traces and debugging
Read a C# compiler error by its file, line and id, fix a build in rounds, trace an exception to the failing line, and find a wrong value with a debugger.
What you will learn
- Decode a compiler diagnostic by its file, line, column, severity, id and message
- Fix a failing build in rounds from its first error, and make warnings fail the build
- Read an unhandled exception's stack trace down to the first line of your own code
- Explain why a stack overflow and a failed Debug.Assert end the process, and when an assertion runs at all
- Use a breakpoint, stepping and a watch in a debugger to find a wrong value
Before you start
What you’ll learn · Before you start
What you will learn
- Decode a compiler diagnostic by its file, line, column, severity, id and message
- Fix a failing build in rounds from its first error, and make warnings fail the build
- Read an unhandled exception's stack trace down to the first line of your own code
- Explain why a stack overflow and a failed Debug.Assert end the process, and when an assertion runs at all
- Use a breakpoint, stepping and a watch in a debugger to find a wrong value
Before you start
On this page
The first build of a nine-line C# program with three mistakes in it reported one error, not three. Every problem in C# reaches you as text like that: a diagnostic: A message from a build tool about your code: its file, line and column, a severity (error or warning), an id such as CS1002 and a text. An error stops the build; a warning does not. from the compiler while you build, or a stack trace: The list of method calls that were running when an exception was thrown, newest first, each with its file and line when debug symbols are available. An unhandled exception prints it to standard error. from the runtime when the program fails while it runs.
This lesson takes both apart, fixes a build in rounds, follows an exception from the line that threw it back to the
start of the program, shows two failures that end a program before any catch can run, and finds a wrong value with
a debugger instead of guessing.
A diagnostic says where, how bad and what
This program adds up the prices on a receipt. It has three mistakes, of the kind everybody types:
// Adds up the prices on a receipt. It has three mistakes.
int[] prices = [120, 80, 45];
int total = 0;
foreach (int price in prices)
{
total += price
}
string count = prices.Length;
Console.WriteLine($"{count} items, total {totl}"); Most folders of this lesson’s examples hold a small console project, like the ones of the previous lesson. The
session builds this one, then two corrected copies of it, and runs the last; -v q (quiet) keeps the build’s own progress
lines out of the way:
from session import run
# The receipt program built three times: with its three mistakes, after the first fix, and after all three, which
# then runs. -v q (quiet) leaves out the restore and progress lines, so only the diagnostics and the summary are
# printed.
run("""
cd round-1
!dotnet build -v q
cd ../round-2
!dotnet build -v q
cd ../round-3
dotnet build -v q
dotnet run
""") Output
$ cd round-1
$ dotnet build -v q
round-1/Program.cs(6,19): error CS1002: ; expected [round-1/Receipt.csproj]
Build FAILED.
round-1/Program.cs(6,19): error CS1002: ; expected [round-1/Receipt.csproj]
0 Warning(s)
1 Error(s)
Time Elapsed …
[exit status 1]
$ cd ../round-2
$ dotnet build -v q
round-2/Program.cs(8,16): error CS0029: Cannot implicitly convert type 'int' to 'string' [round-2/Receipt.csproj]
round-2/Program.cs(9,43): error CS0103: The name 'totl' does not exist in the current context [round-2/Receipt.csproj]
Build FAILED.
round-2/Program.cs(8,16): error CS0029: Cannot implicitly convert type 'int' to 'string' [round-2/Receipt.csproj]
round-2/Program.cs(9,43): error CS0103: The name 'totl' does not exist in the current context [round-2/Receipt.csproj]
0 Warning(s)
2 Error(s)
Time Elapsed …
[exit status 1]
$ cd ../round-3
$ dotnet build -v q
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed …
$ dotnet run
3 items, total 245
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 errors_session.py
The first build printed one line of interest, twice: once as it happened and once in the summary under
Build FAILED. Every diagnostic has the same parts. Take the first one apart:
| Part | In the first diagnostic | What it tells you |
|---|---|---|
| File | round-1/Program.cs |
the file to open |
| Line and column | (6,19) |
line 6, character 19: just after price |
| Severity | error |
an error stops the build; a warning does not |
| Id | CS1002 |
the code to look up in the documentation |
| Message | ; expected |
what the compiler expected and did not find |
| Project | [round-1/Receipt.csproj] |
the project that was being built |
The letters of the id say which tool is talking, which tells you where to look for the cause:
| Id starts with | Reported by | Seen in this track |
|---|---|---|
CS |
the C# compiler | CS1002 here; CS8602, a warning about null, later in this lesson |
CA, IDE |
the code analyzers that come with the SDK: quality and style rules | CA2266 in the lesson on file-based app directives |
NU |
NuGet, while it restores packages | NU1301 when restore could not reach nuget.org |
NETSDK |
the .NET SDK’s own build steps | NETSDK1022 for a file listed twice, in the previous lesson |
MSB |
MSBuild, the build engine | problems with the build itself rather than your code |
Checkpoint
Check: Reading a diagnostic
Fix the first error, then build again
Round 1 reported CS1002 alone, yet two more mistakes were in the file all along. Once the semicolon was back, the
second build got past the syntax, checked what the code means and found them: CS0029 on line 8, because an int
cannot be stored in a string without a conversion, and CS0103 on line 9, because no name totl exists. The third
build, with int count and total, reported 0 errors, and dotnet run printed 3 items, total 245. Step through
the three rounds:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- Round 1. The first build of the receipt program reports one error, CS1002 at line 6, column 19: a semicolon is missing after total += price. The other two mistakes are not reported yet.
- Read the first one. Start with the first diagnostic in the list. Here it is the only one; in a longer list, later errors are often consequences of the first, so they may vanish with it.
- Fix its cause. Add the semicolon at the end of line 6 and change nothing else, so that the next build tells you whether this fix was the right one.
- Round 2. The second build gets past the syntax and checks what the code means. Two errors appear that were there all along: CS0029 on line 8 and CS0103 on line 9.
- Back to the first error. CS0029: an int cannot be stored in a string, so count becomes an int. CS0103: no name totl exists; it is a typing slip for total. Both fixes go in, then one more build.
- Round 3. The third build reports 0 warnings and 0 errors, and dotnet run prints 3 items, total 245.
Two habits follow from those runs. Start each round at the first diagnostic in the list, because a later error is often only a consequence of an earlier one and goes away with it. And build again after each fix: a fixed syntax error can let mistakes of meaning appear that were hidden behind it.
Key idea
Fix the first diagnostic and build again: later errors may be its consequences, and mistakes of meaning can wait behind a mistake of syntax until it is fixed.
The position in a diagnostic is where the compiler noticed the problem, which is not always where you made the mistake. This file’s loop has lost its closing brace:
// The closing brace of the foreach loop is missing: it belongs after line 11.
Console.WriteLine(Receipt.Total([120, 80, 45]));
static class Receipt
{
public static int Total(int[] prices)
{
int total = 0;
foreach (int price in prices)
{
total += price;
return total;
}
public static int Count(int[] prices) => prices.Length;
} from session import run
# brace, a program whose foreach loop has lost its closing brace, built once.
run("""
cd brace
!dotnet build -v q
""") Output
$ cd brace
$ dotnet build -v q
brace/Program.cs(13,6): error CS1513: } expected [brace/Receipt.csproj]
Build FAILED.
brace/Program.cs(13,6): error CS1513: } expected [brace/Receipt.csproj]
0 Warning(s)
1 Error(s)
Time Elapsed …
[exit status 1]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 brace_session.py
CS1513, } expected, points at line 13, column 6: just after the brace that the compiler took as the end of the
loop. The brace that is really missing belongs after line 11, two lines earlier. Until public static on line 15,
which cannot stand inside a method, every line still made sense to the compiler, so it could only notice the gap
there and report it after the last brace it had read.
Editing every line the list names
With a missing brace or parenthesis, doing what each later message says adds code that was never wrong. Fix the one cause of the first message, build, and read the new list.
Checkpoint
Check: Fixing a build in rounds
Warnings compile, and some of them are bugs
A warning does not stop the build, so the program runs, and sometimes it runs into the very problem the warning named. This program greets the name on the first line of its input:
// Greets the name on the first line of standard input.
string? name = Console.ReadLine();
Console.WriteLine($"Hello, {name.Trim()}!"); from session import run
# greet compiles with one warning. It runs once with a name on standard input and once with no input at all, is built
# a second time without any change, and then greet-strict, the same program with TreatWarningsAsErrors on, is built.
run("""
cd greet
dotnet build -v q
echo Asha | dotnet run
!dotnet run < /dev/null
dotnet build -v q
cd ../greet-strict
!dotnet build -v q
""") Output
$ cd greet
$ dotnet build -v q
greet/Program.cs(3,29): warning CS8602: Dereference of a possibly null reference. [greet/Greet.csproj]
Build succeeded.
greet/Program.cs(3,29): warning CS8602: Dereference of a possibly null reference. [greet/Greet.csproj]
1 Warning(s)
0 Error(s)
Time Elapsed …
$ echo Asha | dotnet run
Hello, Asha!
$ dotnet run < /dev/null
Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.
at Program.<Main>$(String[] args) in greet/Program.cs:line 3
[exit status 134]
$ dotnet build -v q
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed …
$ cd ../greet-strict
$ dotnet build -v q
greet-strict/Program.cs(3,29): error CS8602: Dereference of a possibly null reference. [greet-strict/Greet.csproj]
Build FAILED.
greet-strict/Program.cs(3,29): error CS8602: Dereference of a possibly null reference. [greet-strict/Greet.csproj]
0 Warning(s)
1 Error(s)
Time Elapsed …
[exit status 1]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 warnings_session.py
CS8602 says that name may be null where line 3 calls name.Trim(). With Asha on its input the program greeted
her; with no input at all, Console.ReadLine() returned null and the program stopped with a NullReferenceException
at the very line the warning named. Then comes a trap: the second dotnet build reported 0 warnings, because nothing
had changed, so MSBuild did not run the compiler again, and warnings are only reported by a compiler that runs. A
warning you scrolled past once may never be shown again.
greet-strict is the same program with one more line in its project file,
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>. Its build turned CS8602 into an error and failed, so no program
was written to run. The command-line switch -warnaserror does it for one build, and for the warnings of every build
step rather than the compiler’s alone; <WarningsAsErrors>nullable</WarningsAsErrors> makes only the warnings about
null fail. The real fix is in the code:
string name = Console.ReadLine() ?? ""; gives name a value when the input has ended, and the next lesson reads
input properly.
Making the warning go away with !
name!.Trim() silences CS8602, but the crash stays: Microsoft’s reference says that the null-forgiving operator
has no effect at run time. Handle the null instead, with ?? or an if (name is null) check.
Checkpoint
Check: Warnings and warnings as errors
A stack trace lists the calls from the throw back to the start
A program that compiles can still fail while it runs. This one averages three marks, and one of them was typed with the letter O instead of a zero:
// Averages three marks. The second one was typed with the letter O instead of a zero.
string[] lines = ["72", "9O", "85"];
Console.WriteLine($"Average: {Average(lines)}");
static double Average(string[] lines) => Sum(lines) / (double)lines.Length;
static int Sum(string[] lines)
{
int sum = 0;
foreach (string line in lines)
{
sum += int.Parse(line);
}
return sum;
} Output (exit status 134)
Printed as an error (standard error)
Unhandled exception. System.FormatException: The input string '9O' was not in a correct format. at System.Number.ThrowFormatException[TChar](ReadOnlySpan`1 value) at System.Int32.Parse(String s) at Program.<<Main>$>g__Sum|0_1(String[] lines) in ./marks.cs:line 12 at Program.<<Main>$>g__Average|0_0(String[] lines) in ./marks.cs:line 5 at Program.<Main>$(String[] args) in ./marks.cs:line 3
Recorded with .NET SDK 10.0.401 (runtime 10.0.12) on macOS 26 arm64. To run it yourself: dotnet run marks.cs
Read the trace from the top. The first line names the exception, System.FormatException, and its message, which
quotes the text that failed, '9O'. Each line after it that starts with at is a frame: a method that was running
when the exception was thrown, newest first. The first two frames, System.Number.ThrowFormatException and
System.Int32.Parse, belong to .NET and have no file. The first frame with a file of yours,
Sum(String[] lines) in ./marks.cs:line 12, is where to look first; the frames below it are the callers that led
there, Average at line 5 and the top-level code at line 3. Names such as <<Main>$>g__Sum|0_1 are how the compiler
names a local function of top-level code: read the part after g__. Step through how the program got there and how
the exception came back:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- Line 3 calls Average. The top-level code reaches line 3 and calls Average(lines). The runtime keeps a frame for each call that has not returned yet: the call stack.
- Line 5 calls Sum. Average needs the sum first, so line 5 calls Sum(lines). Now three frames are waiting: the top-level code, Average and Sum.
- int.Parse throws. Sum adds 72, then calls int.Parse on "9O" at line 12. The O is a letter, so int.Parse throws a FormatException, and Sum stops in the middle of its loop.
- Sum has no catch. No try and catch surrounds line 12, so Sum ends at once and the exception goes on to its caller, Average, without a return value.
- Average has none either. Average has no catch for it, so the exception moves up again, to the top-level code at line 3.
- Nothing caught it. No frame caught the exception. The runtime prints Unhandled exception, the type and message, then one at line per frame, newest first: Sum line 12, Average line 5, the top-level code line 3. The process ends with exit status 134.
No catch stopped the exception, so the runtime printed the trace to standard error and ended the process with exit
status 134. The Bash manual gives the rule behind that number: a program ended by signal N exits with 128 + N, and
signal 6 is SIGABRT, the abort signal, which the runtime raised to end this run on macOS.
The file and line numbers come from the .pdb file that the build writes next to the .dll: Microsoft’s compiler
reference gives portable as the default DebugType of both Debug and Release builds. A Release build is optimized,
and the documentation of Exception.StackTrace warns that optimizations such as inlining can leave out some of the
method calls you expect.
Rethrowing with throw ex
In a catch block, throw ex; restarts the stack trace at that line, and the frames down to the original throw are
lost. Write throw; to rethrow the exception as it is. The SDK’s analyzer warns about the first form as CA2200.
Checkpoint
Check: Reading a stack trace
Some failures end the process before any catch can run
Depth below calls itself with no case that stops it, inside a try block whose catch should print what went
wrong:
// Depth calls itself with no case that stops it. The catch block below never runs.
try
{
Console.WriteLine(Depth(1));
}
catch (Exception e)
{
Console.WriteLine($"Caught {e.GetType().Name}");
}
static int Depth(int n) => Depth(n + 1) + 1; Output (exit status 134)
Printed as an error (standard error)
Stack overflow. Repeated 261387 times: -------------------------------- at Program.<<Main>$>g__Depth|0_0(Int32) -------------------------------- at Program.<Main>$(System.String[])
Recorded with .NET SDK 10.0.401 (runtime 10.0.12) on macOS 26 arm64. To run it yourself: dotnet run depth.cs
Caught never appeared. The runtime printed Stack overflow., the one frame it found repeated 261,387 times, and the
frame that made the first call, then ended the process with exit status 134. The number depends on the size of the
thread’s stack and of each frame. Microsoft’s documentation of StackOverflowException is plain about it: you can’t
catch one with try and catch, and the process is terminated, so code that recurses must stop itself, with a case
that ends the recursion or a loop instead.
Debug.Assert ends a process too, but only in some builds. This program checks each mark with it, and
average-checked checks it with an exception instead:
using System.Diagnostics;
int[] marks = [72, 105, 85]; // 105 is not a valid mark
int sum = 0;
foreach (int mark in marks)
{
Debug.Assert(mark is >= 0 and <= 100, $"mark {mark} is out of range");
sum += mark;
}
Console.WriteLine($"Average: {sum / (double)marks.Length:F1}"); int[] marks = [72, 105, 85]; // 105 is not a valid mark
int sum = 0;
foreach (int mark in marks)
{
if (mark is < 0 or > 100)
{
throw new ArgumentOutOfRangeException(nameof(marks), mark, "A mark is from 0 to 100.");
}
sum += mark;
}
Console.WriteLine($"Average: {sum / (double)marks.Length:F1}"); from session import run
# average checks each mark with Debug.Assert; average-checked throws an exception instead. Each runs as a Debug build,
# the default of dotnet run, and as a Release build.
run("""
cd average
!dotnet run
dotnet run -c Release
cd ../average-checked
!dotnet run
!dotnet run -c Release
""") Output
$ cd average $ dotnet run Process terminated. Assertion failed. mark 105 is out of range at Program.<Main>$(String[] args) in average/Program.cs:line 7 [exit status 134] $ dotnet run -c Release Average: 87.3 $ cd ../average-checked $ dotnet run Unhandled exception. System.ArgumentOutOfRangeException: A mark is from 0 to 100. (Parameter 'marks') Actual value was 105. at Program.<Main>$(String[] args) in average-checked/Program.cs:line 7 [exit status 134] $ dotnet run -c Release Unhandled exception. System.ArgumentOutOfRangeException: A mark is from 0 to 100. (Parameter 'marks') Actual value was 105. at Program.<Main>$(String[] args) in average-checked/Program.cs:line 7 [exit status 134]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 assert_session.py
The Debug build, the default of dotnet run, stopped at the assertion: Process terminated., Assertion failed.,
the message and line 7, with exit status 134. The Release build printed Average: 87.3 and exit status 0, the average
of 72, 105 and 85, with the invalid mark included. The methods of Debug carry the Conditional("DEBUG") attribute,
so the compiler leaves their calls out unless the DEBUG symbol is defined, and by default it is defined for Debug
builds only. The exception of average-checked stopped both builds at the same line.
| Check | In a Debug build | In a Release build | Use it for |
|---|---|---|---|
Debug.Assert(condition, message) |
ends the process when false | removed: 105 was averaged | assumptions about your own code while you develop it |
throw new ArgumentOutOfRangeException(…) |
throws | throws the same | input from users, files and other code |
Key idea
A stack overflow cannot be caught, and Debug.Assert does not exist in a Release build: stop recursion yourself,
and check anything that can go wrong in a shipped program with code that throws or returns an error.
Checkpoint
Check: Stack overflows and Debug.Assert
A debugger pauses the program so you can look inside
A wrong value with no error is the hardest kind of bug to see in the output. This program prints the average of two marks:
// Prints the average of two marks: it prints 78, but the average of 72 and 85 is 78.5.
int[] marks = [72, 85];
int sum = 0;
foreach (int mark in marks)
{
sum += mark;
}
double average = sum / marks.Length;
Console.WriteLine($"Average: {average}"); Output
Average: 78
Recorded with .NET SDK 10.0.401 (runtime 10.0.12) on macOS 26 arm64. To run it yourself: dotnet run halves.cs
It printed 78, and the average of 72 and 85 is 78.5. You could add Console.WriteLine calls and run it again until
the wrong step shows, or ask a debugger: A tool that runs a program under its control, so that it can pause the program at chosen lines and show the values of variables, the call stack and any expression you ask about. to stop the program at a line and show you every
variable there. Visual Studio, Rider, and VS Code with Microsoft’s C# extension all include one. Step through a
session in VS Code:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- Set a breakpoint. Click the margin left of line 8, or put the cursor on it and press F9. A red dot marks a breakpoint: the program will pause before that line runs.
- Start debugging. Press F5. The debugger builds the program in its Debug configuration and starts it under its control, so that it can stop it.
- Paused before line 8. The loop has run twice and line 8 is next. The Variables view shows sum = 157, and the Call Stack view shows one frame, the top-level code.
- Step over. Press F10 to run line 8 and pause again before line 9. The Variables view now shows average = 78, not the 78.5 you expected.
- Ask the Watch view. Add sum / marks.Length to the Watch view: it shows 78. Both sides of / are int, so C# divides whole numbers and drops the .5 before the result is stored in the double.
- Try the fix in place. Add a second watch, sum / (double)marks.Length: it shows 78.5. Stop the session with Shift+F5, put the cast into line 8 and run the program again.
A breakpoint: A mark on a line of code that makes a debugger pause the program just before that line runs. A conditional breakpoint pauses only when an expression you give it is true. pauses the program before its line runs. From there, F10 steps over the
current line, F11 steps into a method that the line calls, Shift+F11 steps out to the caller and F5 runs on to the
next breakpoint. These are the default keys of VS Code and Visual Studio; other editors have the same commands,
sometimes on other keys. The Variables view shows the values of the paused frame, the Watch view evaluates any
expression you type each time the program pauses, and the Call Stack view is the stack trace of a program that has not
failed yet. A conditional breakpoint pauses only when an expression you give it is true, such as mark > 100 in a loop
over many marks. For exceptions, the Breakpoints view of the C# debugger in VS Code offers All Exceptions: ticked, it
pauses where an exception is thrown, before any catch handles it, so a catch cannot hide the line from you.
Debug halves.cs yourself
Make a console project with dotnet new console -o halves, replace its Program.cs with halves.cs, open the
folder in VS Code with the C# extension, set a breakpoint on line 8 and press F5. Add sum / marks.Length and
sum / (double)marks.Length to the Watch view, then change line 8 and run it again.
Checkpoint
Check: Breakpoints, stepping and watches
Practise: the kata, and questions on all six ideas
This program has four problems: two that stop the build, one that stops the program while it runs, and one wrong value. Its comments say what it should print:
// Kata: four problems stand between this program and the two lines it should print:
// Rows: 3
// Average: 82.3
string[] rows = ["Asha,72", "Ravi,85", "Meera,9O"];
int total = 0;
foreach (string row in rows)
{
string[] parts = row.Split(',');
int mark = int.Parse(parts[1]);
total += mark
}
double average = total / rows.Length;
Console.WriteLine($"Rows: {row.Length}");
Console.WriteLine($"Average: {average:F1}"); from session import run
# The kata as you get it: its first build.
run("""
cd kata
!dotnet build -v q
""") Output
$ cd kata
$ dotnet build -v q
kata/Program.cs(10,18): error CS1002: ; expected [kata/Kata.csproj]
Build FAILED.
kata/Program.cs(10,18): error CS1002: ; expected [kata/Kata.csproj]
0 Warning(s)
1 Error(s)
Time Elapsed …
[exit status 1]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 kata_session.py
Copy the folder, then fix it round by round: the first build names line 10. Build again after each fix, read the
stack trace when the program fails at run time, and use a watch on total / rows.Length when the average still
comes out wrong. First hint: the last problem is the one halves.cs had.
After the kata, the questions below cover all six ideas, starting with the one your earlier answers show you know least; every choice says why it is right or what it misses:
Practice that adapts to you
Practise this lesson
14 questions about 6 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Fresher · What is the difference between a compiler error and a warning in C#, and what does TreatWarningsAsErrors change?
An error means the compiler cannot build the code, such as a missing semicolon (CS1002) or a name that does not exist
(CS0103): the build fails and no program is written. A warning means the code compiles but probably has a problem,
such as CS8602 when a reference that may be null is used; the build succeeds and the program can still fail at that
line when it runs. Each diagnostic names its file, line and column, its severity and an id. Setting
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> in the project file, or building with -warnaserror, reports
every warning as an error, so a build with warnings fails. Teams do it on build servers because nobody reads the
warnings of a green build, and an up-to-date build does not even show them again.
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Fresher to mid
A service crashes in production. Its log ends without any error line, and its console shows "Stack overflow." with one frame repeated thousands of times. Why did the logging in your catch blocks never run, and how do you find and fix the cause?
-
Fresher to mid
A teammate checked every record of an import with Debug.Assert. Runs from the IDE stopped on bad records, but the production service, a Release build, imported 1,200 invalid rows without a word. Explain what happened and how you would change the checks.
Key takeaways
- A diagnostic names the file, the line and column, the severity, an id whose letters say which tool reported it, and a message that says what was expected.
- Fix the first error and build again: later errors may be consequences, and errors of meaning can wait behind a syntax error, as CS0029 and CS0103 waited behind CS1002.
- A warning can be a crash that compiles; an up-to-date build does not show it again, and
TreatWarningsAsErrorsmakes it fail the build. - A stack trace lists the frames newest first; the first frame with a file of yours is where to look, and an unhandled exception ended these programs with exit status 134.
- A stack overflow cannot be caught, and
Debug.Assertdisappears from Release builds, so input is checked with code that throws or returns an error. - A debugger pauses before a breakpoint’s line and shows variables, watches and the call stack, which found the
integer division in
halves.cs.
Terms in this lesson
- breakpoint
- A mark on a line of code that makes a debugger pause the program just before that line runs. A conditional breakpoint pauses only when an expression you give it is true.
- debugger
- A tool that runs a program under its control, so that it can pause the program at chosen lines and show the values of variables, the call stack and any expression you ask about.
- diagnostic
- A message from a build tool about your code: its file, line and column, a severity (error or warning), an id such as CS1002 and a text. An error stops the build; a warning does not.
- stack trace
- The list of method calls that were running when an exception was thrown, newest first, each with its file and line when debug symbols are available. An unhandled exception prints it to standard error.
References
- Compiler messages (C# reference) (Microsoft Learn)
- Compiler options: errors and warnings (Microsoft Learn)
- Code analysis in .NET (Microsoft Learn)
- Nullable reference type warnings (Microsoft Learn)
- ! (null-forgiving) operator (Microsoft Learn)
- Exception.StackTrace property (Microsoft Learn)
- CA2200: Rethrow to preserve stack details (Microsoft Learn)
- Compiler options: code generation (DebugType, Optimize) (Microsoft Learn)
- StackOverflowException class (Microsoft Learn)
- Debug class (Microsoft Learn)
- Debug a .NET console application (Microsoft Learn)
- Debug code with Visual Studio Code (Visual Studio Code (Microsoft))
- Debugging C# in Visual Studio Code: stopping on exceptions (Visual Studio Code (Microsoft))
- Incremental builds in MSBuild (Microsoft Learn)
- Bash Reference Manual: Exit Status (GNU Project)
- signal(7), Linux manual page (Linux man-pages project)
Report a problem with this lesson
Kept only in this browser. Your Learn progress