C# Exceptions and Resource Cleanup: Handle Failure Without Hiding It
A failed operation needs a clear outcome and a resource needs a clear owner. Learn where to catch errors, when to let them propagate, and how to release resources on both success and failure.
What you will understand
- Trace normal returns, thrown failures and matching catch blocks.
- Distinguish expected validation from a failed helper contract.
- Order specific handlers before their broader parent categories.
- Use finally for cleanup without implying success or rollback.
- Dispose owned resources with using on exception and return paths.
- Rethrow with throw; or preserve an inner cause when translating.
- Reject malformed file data without returning partial success.
- Test successful totals, meaningful failures and explicit disposal traces.
An exception is a failed operation, not an ordinary return value
A method normally returns a result and control continues after the call. If it throws an exception instead, it does not return that normal result. Execution leaves the failing statement and looks for an appropriate handler. Any remaining statements in that try block are skipped.
The first program deliberately parses invalid text. Before parse is printed, then int.Parse throws FormatException. The catch explains the failure and the program continues after the handled try/catch. It does not retry the parse, resume on the next line inside try or create a valid minutes value.
An exception object describes a failure category and may include diagnostic information such as a message, parameter name, cause and stack trace. The type is useful for choosing a handler. Avoid making logic depend on the exact wording of runtime-generated messages, which can vary by platform or localization.
A throw can happen several method calls below the code that initiated the work. Stack unwinding searches outward for a suitable catch and runs applicable cleanup along the way. An unhandled exception can terminate the process, so handle it where the program has enough context to make a meaningful decision.
Program.cs · A handled exception skips the rest of its try block
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Before parse
The text is not an integer
Caller continues
int.Parse throws FormatException, so the next statement inside try is skipped. The matching catch runs and execution then continues after the handled try/catch. It does not resume at the failed parse statement or invent a numeric result.
Use normal checks for expected input and exceptions for failed contracts
A person typing nonnumeric text in a form is an ordinary validation case. int.TryParse gives a Boolean result so the program can ask for a whole number without throwing for every mistake. Check that Boolean before treating the out value as valid data.
A helper can throw an argument exception when its caller violates a declared contract. DoubleMinutes accepts values from zero through six hundred and rejects values outside that range before calculating. It is the helper’s boundary; the caller decides whether it can correct the input or needs to stop that operation.
Expected does not mean impossible to represent as an exception. File APIs may throw for a missing file even when first-time absence is normal for your application. Choose how to interpret the failure at your application boundary instead of assuming every exception indicates a programming bug.
Do not use exceptions as a substitute for every if statement. Conversely, do not convert every failure into false or zero if that hides why the operation did not complete. The final file loader returns a total only when its complete read and validation succeed.
Program.cs · Use a normal result for an expected input mistake
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Enter a whole number
Invalid form input is ordinary enough to handle with TryParse rather than throwing and catching for each failed attempt. The Boolean must be checked before the out value is accepted as input. A failure is not a valid zero-minute session.
Program.cs · Reject an invalid argument before changing state
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Rejected parameter: minutes
The helper declares its nonnegative-input contract explicitly and throws before calculating an answer. Main handles this particular demonstration failure. nameof(minutes) identifies the argument in a stable diagnostic; the lesson does not compare localized exception message text.
Catch the failure you can explain or recover from
Put the smallest meaningful operation inside try, then catch categories that this layer can handle. A missing optional file may lead to a first-use message. A file containing invalid records should lead to a data problem, not be described as missing. A permission failure should not silently become an empty list.
Catch clauses are checked from specific to broad types. FileNotFoundException derives from IOException, so its special handler belongs before catch (IOException). Only the matching selected handler runs for that exception. The compiler refuses a later handler already covered by an earlier broader catch.
catch (Exception) at every helper is usually too broad: it can hide unrelated bugs and leave the caller with invented results. A top-level application may need a last-resort boundary for logging and a failed response, but that is different from claiming that a lower-level operation recovered successfully.
Catching an exception is not automatically recovery. Recovery means the program has an explicit valid next step, such as asking for corrected input or refusing to produce a report. If this layer cannot choose that step, let the failure propagate rather than concealing it.
Program.cs · Handle a specific failure before its broader parent type
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
No saved file yet
FileNotFoundException derives from IOException. The more specific handler comes first and describes a meaningful first-use case. Only the selected handler runs; catch blocks are not a sequence of notifications for the same exception. The explicit throw isolates this type-matching rule from filesystem conditions.
Program.cs · A broad catch first makes a later specific catch unreachable
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Compilation fails: a previous catch already handles FileNotFoundException through IOException.The broader catch captures every exception that the later specific catch could handle, so the compiler rejects this ordering. Put specific categories before their broader parent categories instead of changing the exception type to silence the diagnostic.
Finally is about cleanup, not success or rollback
A finally block runs when ordinary control leaves its protected try/catch, whether the operation succeeds, throws or returns. In the trace example, work fails, the catch handles it, and the finally message appears before execution continues.
Use finally for cleanup that belongs to this scope. Do not put a success message there: cleanup also runs on failure. Do not return or throw a new avoidable error from cleanup, because that can hide the original failure. Disposal itself can fail, so designs with important cleanup errors need deliberate diagnostic handling.
Finally is not a transaction. If the program wrote half a file before failure, finally does not restore the previous contents automatically. Likewise a catch does not undo messages already sent or records already saved. Atomic writes and transactions are separate design tools.
Cleanup guarantees describe ordinary runtime control flow, not every way a process can disappear. A forced process kill or machine failure can prevent cleanup code from executing. Prefer a clear scoped lifetime while recognizing that durable recovery needs more than a finally block.
Program.cs · Finally runs during normal exception unwinding
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Work starts
Failure handled
Cleanup step
After cleanup
finally runs after the selected catch in this example. It is for cleanup that must happen when ordinary control leaves the protected operation, whether work succeeded or failed. It does not mean the operation succeeded, undo changes, or guarantee cleanup after a forced process termination.
Use using for resources that your code owns
A StreamReader owns an underlying file handle, and a StreamWriter may buffer text before writing it. These resources should be disposed when their work ends. Garbage collection is not a prompt or predictable substitute for releasing such owned resources.
A using block makes that lifetime explicit with braces. A using declaration disposes the resource at the end of its enclosing scope. Both arrange cleanup when an exception leaves the scope or when a return exits it. Do not confuse these constructs with a using System directive, which simply brings names into scope.
TraceResource implements IDisposable only to make the cleanup call visible in output. The failure example prints Resource disposed before its outer catch handles the problem. The early-return example disposes before the caller receives the returned value. These deterministic traces teach the rule without depending on platform-specific file locking.
Only dispose what your code owns according to the API contract. A method that creates its own StreamReader normally owns it. A method handed a borrowed stream by its caller must not assume it can close that caller-owned resource. The final loader creates and owns its reader inside the method.
Program.cs · Using disposes a resource when a block exits on failure
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Inside using
Resource disposed
Caller handles bad data
TraceResource is a tiny teaching implementation of IDisposable. Its Dispose message proves the cleanup call happened before the outer catch handled the failure. It represents the same lifetime rule that closes an owned StreamReader, without depending on operating-system file-lock behavior.
Program.cs · A return still leaves through the using cleanup
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Resource disposed
Result: 7
The return expression is evaluated, then disposal happens before the helper returns to its caller. A using declaration lasts until the end of its enclosing scope, unlike a using block whose braces make the shorter lifetime explicit. Neither is the same as the using System directive at the top of a file.
Rethrow without losing the original cause
If a catch adds useful local diagnostics but cannot recover, use throw; inside the catch to propagate the same exception. That preserves its original stack trace. Writing throw error restarts stack information at that rethrow point and makes the original failure harder to locate.
The Load example prints that invalid data was observed, then rethrows it. Main receives the same failure category. The local catch is included to demonstrate propagation; in a real system, omit it if it adds no useful context and let the exception travel naturally.
Sometimes a higher-level operation needs to translate the failure into its own vocabulary. The wrapper example throws InvalidOperationException with the original InvalidDataException supplied as the inner exception. This preserves a cause chain while intentionally changing the type callers must handle.
Do not wrap every failure just to change its message, and do not expose sensitive filesystem paths or credentials in user-facing diagnostics. Separate a clear user message from detailed diagnostic records. These examples print only fixed teaching text and stable category or parameter information.
Program.cs · Add context locally, then preserve the exception
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Loader observed invalid data
Caller received: InvalidDataException
The inner catch records local context and uses throw; to continue the same failure. It does not claim recovery. Bare throw preserves the original stack trace; throw error resets stack information at the rethrow site. Do not use the latter just because you have an exception variable.
Program.cs · Translate a failure only when the new context is useful
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Cannot build the practice summary.
Cause: InvalidDataException
The helper changes the abstraction-level error but retains its cause with the inner-exception constructor argument. Translation is deliberate: callers now see InvalidOperationException rather than InvalidDataException. Do not wrap every exception indiscriminately or discard the original cause.
Define what a successful file load actually means
The lab file has one integer minute count per line. It accepts zero through six hundred per record and at most ten records. Empty files legitimately total zero, while blank lines, nonnumeric text, negative values and excessive values are invalid data. int.TryParse uses its ordinary integer syntax here, including outside whitespace; this is not a strict digits-only wire format.
The bound of ten records times six hundred minutes limits the total to six thousand. The loader can therefore use int addition without an unbounded accumulation policy. If the application removes those bounds, it must revisit arithmetic overflow and memory or processing limits instead of assuming the same implementation is safe for any input.
LoadMinutes returns only after the full file has been read and validated. A bad later line must not return a partial total as if it described the whole file. The exception includes a one-based line number so the caller can identify which record to correct.
Main distinguishes missing files, invalid data, I/O failures and denied access. The practice setup creates a known valid fixture; tests replace it with other cases. Files are confined to the temporary execution folder and discarded after each website run. A local console project can retain its own practice file across runs.
Test success, failure and cleanup as separate outcomes
A successful example is not enough. Test a valid minutes file, a genuinely empty one, a missing path, malformed first and later lines, negative or excessive values and more than ten records. Ensure failure does not print Total minutes or return a partial summary.
For cleanup, verify that leaving an owned using scope invokes disposal on normal completion, an exception and an early return. Do not use the ability to delete an open file as universal proof: operating-system sharing and deletion rules differ. The TraceResource examples make the cleanup event explicit.
The guided repair removes an empty catch that prints false success. The lab requires both a correct returned total and clear failure behavior. A test should check the message, whether a total was produced and which error category escaped, rather than accepting any exception as the expected one.
This completes the planned C# course sequence. Revisit the file lesson and build a small local reading-list or practice-log program: validate input, preserve object rules, save deliberately and explain each failure honestly. The course examples demonstrate these building blocks; they do not claim production persistence, concurrency control or a complete account-backed application.
Guided repair: stop reporting false success
The program catches an I/O failure and then prints Loaded successfully. Remove the false-success path and report that no summary was produced.
Program.cs · Swallowing a failure reports false success
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Loaded successfully
The catch silently discards the error, and the success line runs even though loading failed. There was no recovery or valid fallback. An empty catch can hide data loss or an unavailable dependency while presenting an apparently healthy workflow.
Repair checkpoints
- Identify the empty IOException catch as discarded failure information.
- Choose an explicit failure outcome instead of pretending the load completed.
- Keep success output only after real successful work.
- Do not return zero or an empty list as a substitute for an unavailable file unless that is a stated valid policy.
- Verify the demonstration prints only its clear failure message.
Reveal the repair and explanation
Program.cs · Report failure and keep success inside the successful path
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Could not load the practice file; no summary produced
The caller reports a known failure without claiming a valid summary. In a real loader, successful output belongs after successful reading and validation, inside that success path. Retrying or continuing should be an explicit caller policy, not an automatic consequence of catching an exception.
Independent lab: load a complete valid minutes summary
Complete a bounded minutes-file loader. Read with an owned StreamReader, validate every record and return a total only after the whole input succeeds.
Acceptance checklist
- Create and own StreamReader with using inside LoadMinutes.
- Accept an empty file as total zero.
- Read up to ten lines, each parsed integer between zero and six hundred inclusive.
- Reject malformed, blank, negative or excessive records with InvalidDataException and a one-based line number.
- Reject an eleventh record rather than silently truncating the input.
- Return the sum only after full successful reading; do not return partial totals on failure.
- Let missing, permission and other file failures reach the caller’s appropriate handlers.
- Preserve Total minutes: 35 for the supplied 25, 10 and 0 fixture.
Program.cs · Bounded minutes-file loader starter
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Total minutes: 0
The caller already distinguishes expected failure categories. The loader is a placeholder that returns zero without reading. Implement the input contract so a real empty file may return zero, but a missing or malformed file cannot masquerade as zero practice.
Show hints
- Keep lineNumber and total local to each invocation.
- Put the reader in a using declaration before the read loop.
- Use one guard combining TryParse failure with the allowed numeric range.
- The caller already prints success only after LoadMinutes returns normally.
Reveal the reference solution
Program.cs · Bounded minutes-file loader reference solution
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Total minutes: 35
LoadMinutes owns its reader through using and validates every record before adding it. At most ten values of at most six hundred keep the total bounded by six thousand. Failure exits without returning a partial total, and disposal happens on both successful and exceptional paths. The caller prints a success only after the helper returns normally.
Changed-input checks
- An empty file totals 0; 0 and 600 are accepted boundary records.
- A malformed second line identifies line 2 and returns no partial total.
- Blank, -1 and 601 records are rejected as invalid data.
- Eleven otherwise valid records are refused.
- A missing file reaches the missing-file message.
- The owned reader is disposed on both successful and exceptional exits.
Recap: failure policy and resource ownership
Identify the valid result, the failure category, the layer that can recover and the scope that owns cleanup.
try- Protect a focused operation that may throw.
catch (SpecificException)- Handle a category when this layer can choose a meaningful next step.
finally- Perform cleanup during ordinary scope exit, not automatic rollback.
TryParse- Represent expected invalid text with a checked Boolean result.
throw new ...- Signal a failed contract with an appropriate exception type.
throw;- Propagate the current caught exception while preserving its original stack.
InnerException- Retain the original cause when deliberately translating a failure.
using- Dispose an owned resource at block or scope exit.
Frequently asked questions
Does catch resume the failed statement?
No. A matching handler runs, then normal flow continues after the handled construct. Retrying requires an explicit policy.
Should I use exceptions for every invalid form entry?
Usually use normal validation such as TryParse for expected bad input. Some library operations still signal expected application conditions with exceptions, which a suitable boundary can interpret.
Does finally mean the operation succeeded?
No. It runs on ordinary success and failure paths for cleanup. It does not roll back earlier changes or guarantee execution after a forced process termination.
Does using catch the exception for me?
No. It arranges disposal of an owned resource when scope exits. The exception still propagates unless another handler catches it.
Why use throw; instead of throw error?
Bare throw in a catch preserves the original stack trace. Throwing the variable resets stack information at that rethrow site.
Is a missing file the same as an empty one?
No. An empty existing file may be a valid zero-record input. A missing file means reading did not occur and needs its own application policy.
Should a loader return a partial total after a bad line?
Only if its contract explicitly defines partial results and reports their limits. This lab deliberately returns a total only for a fully valid file.
Can you trace failure and cleanup?
Trace catch selection, finally, using, rethrowing and all-or-nothing report validation. Read the explanation after checking your answers.
Course practice and references
A catch needs a meaningful failure policy, and a resource needs a clear owner. Validate expected input, let failures reach a suitable boundary, preserve causes and use scoped disposal instead of hiding problems.
You have reached the final lesson in the planned C# curriculum. Practice by building a small local reading-list or minutes-log program, combining validated input, clear object contracts, deliberate file writes and honest failure handling.
Official references
Original SovranCode explanations, examples and exercises. Technical details were checked against these references.
Last updated: .