Objective 1.3 — Incorporate Error Handling into
Applications or Modules
Lecturer Version (with diagrams and VS Code debugging annotations)
Prepared for teaching with the Grade App (C# Console)
February 18, 2026
What learners will be able to do (Objective 1.3)
By the end of this lesson, learners can:
• Use try–catch–finally correctly and explain the flow.
• Throw built-in and custom exceptions at the correct layer.
• Apply defensive coding (validation, TryParse, null-safe input).
• Explain scoping issues and why variables must sometimes be declared outside try.
• Read a stack trace to locate where an error occurred.
• Use Console.WriteLine vs Console.Error.WriteLine appropriately.
• Debug in VS Code using breakpoints, Step Into, Step Over.
• Identify what to unit-test and create a test plan.
1
The reference code (Grade App)
Put your working C# program in code/Program.cs and (optionally) include it here:
Tip
If you prefer a shorter PDF, remove this code listing section and keep only the explanation
sections.
Program.cs (excerpt or full)
1
2
3
4
5
6
7
8
9
10
//
//
//
//
//
//
//
//
//
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Program . cs
Practical demo for Objective 1.3: Error handling in C #
Covers :
- Structured exception handling ( try / catch / finally )
- Throwing exceptions ( custom + built - in )
- Defensive coding ( validation , TryParse , null checks )
- Scoping in exception handling ( variables visible in finally )
- Writing to console ( Console . WriteLine vs Console . Error )
- Reading the stack ( StackTrace )
1
11
12
// - Debugging in VS Code ( breakpoints , Step Into , Step Over )
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
13
14
15
using System ;
using System . IO ;
16
17
# region 1) CUSTOM EXCEPTION ( Throwing exceptions , domain - specific
errors )
18
19
20
21
22
23
24
25
26
// / < summary >
// / Custom exception : used when a score is outside the valid range
(0..100) .
// / This makes the error more meaningful than a generic Exception .
// / </ summary >
public class I n v a l i d S c o r e E x c e p t i o n : Exception
{
// Extra data carried by the exception ( useful for logging or UI
messages )
public int Score { get ; }
27
public I n v a l i d S c o r e E x c e p t i o n ( int score )
: base ( $ " Score must be between 0 and 100. Got : { score } " )
{
Score = score ;
}
28
29
30
31
32
33
}
34
35
# endregion
36
37
# region 2) DOMAIN / BUSINESS LOGIC ( Unit - testable module )
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// / < summary >
// / Pure logic class : easy to unit test because it does not read from
console
// / or write to files . It only converts a numeric score into a letter
grade .
// / </ summary >
public class GradeCalculator
{
// / < summary >
// / Converts a numeric score into a letter grade .
// / Demonstrates defensive coding + throwing a custom exception .
// / </ summary >
public string GetLetterGrade ( int score )
{
// DEFENSIVE CODING :
// Validate inputs BEFORE continuing . Never assume input is
correct .
if ( score < 0 || score > 100)
{
// THROWING EXCEPTIONS :
// Stop normal execution and signal an invalid score .
throw new I n v a l i d S c o r e E x c e p t i o n ( score ) ;
}
59
60
61
62
// Decision logic ( normal program path , no exception )
if ( score >= 90) return " A " ;
if ( score >= 80) return " B " ;
2
if ( score >= 70) return " C " ;
if ( score >= 60) return " D " ;
return " F " ;
63
64
65
}
66
67
}
68
69
# endregion
70
71
# region 3) FILE MODULE ( try / catch / finally , scoping , IO exceptions )
72
73
74
75
76
77
78
79
// / < summary >
// / Handles saving results to a file .
// / Demonstrates try / catch / finally and scoping of variables used in
finally .
// / </ summary >
public class GradeFileWriter
{
private readonly string _filePath ;
80
81
82
83
84
85
public
{
//
//
if
86
GradeFileWriter ( string filePath )
DEFENSIVE CODING :
Ensure file path is not null / empty .
( string . Is Nu ll OrW hi te Sp ac e ( filePath ) )
throw new A rgumen tExcep tion ( " File path cannot be empty . " )
;
87
_filePath = filePath ;
88
89
}
90
91
92
93
94
95
96
97
98
99
100
101
102
103
// /
// /
// /
// /
// /
// /
< summary >
Saves a student ’ s grade to a text file .
Demonstrates :
- try / catch / finally
- handling U n a u t h o r i z e d A c c e s s E x c e p t i o n and IOException
- scoping : writer declared outside try so i t s visible in
finally
// / </ summary >
public void SaveGrade ( string studentName , string grade )
{
// DEFENSIVE CODING :
// Ensure student name and grade are meaningful before saving
.
if ( string . Is Nu ll OrW hi te Sp ac e ( studentName ) )
throw new A rgumen tExcep tion ( " Student name cannot be empty
.");
104
105
106
if ( string . Is Nu ll OrW hi te Sp ac e ( grade ) )
throw new A rgumen tExcep tion ( " Grade cannot be empty . " ) ;
107
108
109
110
// SCOPING :
// writer declared OUTSIDE try so it can be accessed in
finally .
StreamWriter writer = null ;
111
112
113
114
try
{
// Potential failure points :
3
// - unauthorized access
// - file in use
// - invalid path
writer = new StreamWriter ( _filePath , append : true ) ;
115
116
117
118
119
// Writing to the file
writer . WriteLine ( $ " { DateTime . Now : yyyy - MM - dd HH : mm : ss } | {
studentName } : { grade } " ) ;
120
121
122
// Normal message goes to stdout
Console . WriteLine ( "
Grade saved successfully . " ) ;
123
124
}
catch ( U n a u t h o r i z e d A c c e s s E x c e p t i o n ex )
{
// Error message goes to stderr ( useful for logs /
pipelines )
Console . Error . WriteLine ( "
Cannot write to file (
permission issue ) . " ) ;
Console . Error . WriteLine ( " Message : " + ex . Message ) ;
125
126
127
128
129
130
131
// Reading the stack trace ( great for debugging )
Console . Error . WriteLine ( " StackTrace :\ n " + ex . StackTrace ) ;
132
133
}
catch ( IOException ex )
{
Console . Error . WriteLine ( "
File IO error occurred . " ) ;
Console . Error . WriteLine ( " Message : " + ex . Message ) ;
Console . Error . WriteLine ( " StackTrace :\ n " + ex . StackTrace ) ;
}
finally
{
// FINALLY :
// Always runs whether success or exception happened .
// Cleanup resources here .
writer ?. Close () ;
134
135
136
137
138
139
140
141
142
143
144
145
146
147
Console . WriteLine ( "
File operation complete (
finally block executed ) . " ) ;
148
}
149
150
}
151
152
153
154
155
156
157
158
159
160
161
162
// / < summary >
// / OPTIONAL : Cleaner alternative to try / finally for file closing
.
// / Uses " using " statement which guarantees disposal .
// / Keep for teaching best practice .
// / </ summary >
public void SaveGradeUsing ( string studentName , string grade )
{
if ( string . Is Nu ll OrW hi te Sp ac e ( studentName ) )
throw new A rgumen tExcep tion ( " Student name cannot be empty
.");
if ( string . Is Nu ll OrW hi te Sp ac e ( grade ) )
throw new A rgumen tExcep tion ( " Grade cannot be empty . " ) ;
163
164
165
try
{
4
// Using automatically disposes ( closes ) the writer even
if exceptions happen .
using var writer = new StreamWriter ( _filePath , append :
true ) ;
writer . WriteLine ( $ " { DateTime . Now : yyyy - MM - dd HH : mm : ss } | {
studentName } : { grade } " ) ;
Console . WriteLine ( "
Grade saved successfully ( using ) . "
);
166
167
168
169
}
catch ( Exception ex )
{
Console . Error . WriteLine ( "
Unexpected file error ( using
version ) . " ) ;
Console . Error . WriteLine ( " Message : " + ex . Message ) ;
Console . Error . WriteLine ( " StackTrace :\ n " + ex . StackTrace ) ;
}
170
171
172
173
174
175
176
}
177
178
}
179
180
# endregion
181
182
# region 4) MAIN PROGRAM ( TryParse , multiple catch blocks , safety net )
183
184
185
186
187
188
189
190
191
192
193
194
195
196
public class Program
{
public static void Main ()
{
// TIP FOR VS CODE DEBUGGING :
// Put breakpoints on :
// 1) TryParse line
// 2) GetLetterGrade call
// 3) throw I n v a l i d S c o r e E x c e p t i o n
// 4) fileWriter . SaveGrade call
// Then use :
// - Step Over ( F10 ) : run the line without entering a method
// - Step Into ( F11 ) : go inside the method
197
198
199
var calculator = new GradeCalculator () ;
var fileWriter = new GradeFileWriter ( " grades . txt " ) ;
200
201
Console . WriteLine ( " === Grade App ( Objective 1.3 Practical )
=== " ) ;
202
203
204
Console . Write ( " Enter student name : " ) ;
string name = Console . ReadLine () ?? " " ; // defensive : avoid
null
205
206
207
Console . Write ( " Enter score (0 -100) : " ) ;
string input = Console . ReadLine () ?? " " ; // defensive : avoid
null
208
209
210
211
212
213
214
// STRUCTURED EXCEPTION HANDLING :
// This try contains anything that might fail ( input parsing ,
grading , file write )
try
{
// DEFENSIVE CODING : Validate name
if ( string . Is Nu ll Or Whi te Sp ac e ( name ) )
5
throw new A rgumen tExcep tion ( " Student name cannot be
empty . " ) ;
215
216
// DEFENSIVE CODING :
// Use TryParse to avoid FormatException from int . Parse
if (! int . TryParse ( input , out int score ) )
throw new A rgumen tExcep tion ( $ " Score must be a whole
number . You typed : ’{ input } ’ " ) ;
217
218
219
220
221
// This may throw I n v a l i d S c o r e E x c e p t i o n if score is out
of range
string grade = calculator . GetLetterGrade ( score ) ;
222
223
224
Console . WriteLine ( $ "
{ grade } " ) ;
225
{ name } scored { score }
Grade :
226
// This may throw IO - related exceptions or
Arg umentE xcepti on
fileWriter . SaveGrade ( name , grade ) ;
227
228
229
// Optional : show alternative using - based save
// fileWriter . SaveGradeUsing ( name , grade ) ;
230
231
}
catch ( I n v a l i d S c o r e E x c e p t i o n ex )
{
// SPECIFIC catch first : domain rule violation
Console . Error . WriteLine ( "
Invalid score ! " ) ;
Console . Error . WriteLine ( " Message : " + ex . Message ) ;
Console . Error . WriteLine ( " Bad score value : " + ex . Score ) ;
232
233
234
235
236
237
238
239
// Stack trace for debugging
Console . Error . WriteLine ( " StackTrace :\ n " + ex . StackTrace ) ;
240
241
}
catch ( Ar gumen tExcep tion ex )
{
// SPECIFIC catch : input / argument validation errors
Console . Error . WriteLine ( "
Bad input / argument . " ) ;
Console . Error . WriteLine ( " Message : " + ex . Message ) ;
Console . Error . WriteLine ( " StackTrace :\ n " + ex . StackTrace ) ;
}
catch ( Exception ex )
{
// GENERIC SAFETY NET
always last
Console . Error . WriteLine ( "
Unexpected error occurred . " )
;
Console . Error . WriteLine ( " Message : " + ex . Message ) ;
Console . Error . WriteLine ( " StackTrace :\ n " + ex . StackTrace ) ;
}
finally
{
// finally can also be used in Main when you need cleanup
.
// Not required here , but included to show the principle .
Console . WriteLine ( " === Program finished ( finally executed
) === " ) ;
}
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
}
263
264
}
6
265
# endregion
266
2
Structured exception handling: try–catch–finally
Concept
try runs risky code. catch handles specific problems. finally runs always (success or
failure) and is used for cleanup.
2.1
Where it appears in the code
• Main(): handles input parsing, grading, and file saving using multiple catch blocks.
• GradeFileWriter.SaveGrade(): uses try/catch/finally to ensure the file is closed.
2.2
Flow diagram (what happens during errors)
Start Main()
Enter try
TryParse input
bad number
ok
GetLetterGrade(score)
catch ArgumentException
out of range
catch InvalidScoreException
ok
SaveGrade(name, grade)
file issue
catch IOException / UnauthorizedAccessException
(inside SaveGrade)
Success path
Print results
finally
cleanup / final messages
End
7
3
Throwing exceptions (custom + built-in)
3.1
Custom exception: InvalidScoreException
Why custom exceptions?
A custom exception communicates a domain rule clearly: scores must be 0..100. It can
also carry extra data (Score) for better error messages and logging.
In GradeCalculator.GetLetterGrade(int score):
• If score is < 0 or > 100, we throw InvalidScoreException.
• That exception is then caught in Main() and printed.
3.2
Built-in exception: ArgumentException
Why ArgumentException here?
Non-numeric input is not a “grade rule” problem, it’s an invalid argument problem. So
we throw ArgumentException when TryParse fails.
4
Defensive coding (prevent crashes before they happen)
Defensive coding checklist (used in the program)
• Use TryParse instead of Parse.
• Validate ranges and rules before doing work.
• Handle null / empty input safely.
• Keep logic in testable modules (GradeCalculator).
Practical mini-lab
Ask learners to run the app with:
• Input: abc ⇒ ArgumentException
• Input: -5 ⇒ InvalidScoreException
• Input: 101 ⇒ InvalidScoreException
5
Reading the stack trace (how to locate the bug)
What the stack trace tells you
It shows where the exception occurred and the call path that led to it. In this app, errors
typically originate in:
• GradeCalculator.GetLetterGrade (score out of range)
• GradeFileWriter.SaveGrade (file access / IO issues)
8
Teaching tip
Temporarily print ex.StackTrace in catch blocks during teaching. In production, you
would log it (to a file or logging system), not show it to end users.
6
Scoping in exception handling (why writer is declared outside
try)
Key idea
A variable exists only in its scope. If you need a resource in finally, declare it where
finally can see it.
6.1
Diagram: scope visibility
Correct: writer visible in finally
StreamWriter writer = null;
try { writer = new StreamWriter(...); }
finally { writer?.Close(); }
7
Wrong: writer not visible in finally
try { StreamWriter writer = ...; }
finally { writer.Close(); }
(compile error: writer out of scope)
Console output: normal vs error
Rule of thumb
Console.WriteLine = normal status output.
Console.Error.WriteLine = error messages (better for logs and CI pipelines).
8
Debugging in VS Code (breakpoints, Step Into, Step Over)
Required VS Code keys
• Start Debugging: F5
• Step Over: F10
• Step Into: F11
• Step Out: Shift+F11
• Toggle Breakpoint: click gutter or F9
8.1
What to set breakpoints on (in Program.cs)
Recommended lines:
• The TryParse check (to inspect input and score)
• The call GetLetterGrade(score) (use Step Into here)
9
• The line that throws InvalidScoreException
• The call SaveGrade(name, grade) (to observe file handling)
• Each catch block (to see which one catches the error)
9
Annotated VS Code screenshots (replace images with your
real captures)
How to create the screenshots
In VS Code:
• Open Program.cs
• Place breakpoints
• Run debugging ( F5 )
• Trigger errors by typing abc or -5
• Use Step Into / Step Over and capture the screen
Save images to images/ as PNG: vs_breakpoint.png, vs_step_into.png, vs_watch.png,
vs_stacktrace.png
Screenshot 1 — Breakpoints
Screenshot 2 — Step Into vs Step Over
Screenshot 3 — Watch & Variables
Screenshot 4 — Stack Trace
10
Unit testing (what to test, even before writing tests)
What is unit-testable here?
GradeCalculator.GetLetterGrade is pure logic (no console/file I/O), so it is ideal for
unit tests.
10
Suggested test cases
11
Input score
Expected
95
85
75
65
50
-1
101
A
B
C
D
F
throws InvalidScoreException
throws InvalidScoreException
Lecturer delivery notes (how to teach this practically)
Recommended lesson flow (45–60 min)
1. Run the program with valid input (show normal path).
2. Run with abc (ArgumentException) and discuss defensive coding.
3. Run with -5 (InvalidScoreException) and show custom exceptions.
4. Show finally always runs using the file write section.
5. Turn on stack trace printing and explain how to read it.
6. Debug in VS Code: breakpoints, Step Over vs Step Into.
7. End with test plan and what would be unit-tested first.
Common student mistakes
• Putting a semicolon after catch(...) like catch(...)
;
• Catching Exception before specific exceptions (wrong order)
• Using int.Parse and crashing on non-numeric input
• Declaring file resources inside try and failing to close them safely
11
0
You can add this document to your study collection(s)
Sign in Available only to authorized usersYou can add this document to your saved list
Sign in Available only to authorized users(For complaints, use another form )