An exception changes control flow after a specific operation fails
An exception reports that an operation could not complete through its ordinary return path. Here decimal.Parse promises a decimal result only for text matching its accepted numeric grammar. When a string cannot be interpreted as a decimal, Parse throws FormatException instead of returning a guessed value. A matching catch block can handle that failure at a boundary chosen by the program. The placement of that boundary determines whether one rejected record stops the entire batch or only that record's work.
The try/catch/finally sits inside the foreach loop. Consequently, each input text gets its own attempt and its own completion message. InvariantCulture fixes the parsing convention for the machine-style decimal text rather than inheriting a device's regional decimal separator. Parsing and output formatting are still separate decisions: the prepared browser runtime also uses invariant culture for the interpolated result. A portable standalone application should make its output culture explicit when a machine-readable report needs the same spelling everywhere.
Follow both records through the same boundary
For the first input, 12.50, parsing succeeds and the try block prints ok 12.50. The matching catch is skipped. Control then passes through finally, which prints checked. For the second input, twelve, decimal.Parse throws before the price variable can supply a normal result to WriteLine. The rest of that try block is skipped, and the FormatException catch prints not a number: twelve. Finally then prints a second checked line.
The original output is therefore four lines in order: ok 12.50, checked, not a number: twelve, checked. There is no successful price line for the word twelve, and the failed conversion does not silently become zero. Because the handler is inside the loop, a later valid string would still get its own attempt. The finally message records leaving that attempt; it is not evidence that both conversions succeeded or that the catch somehow repaired the malformed numeric text.
catch (FormatException)
{
Console.WriteLine($"not a number: {text}");
}
finally
{
Console.WriteLine("checked");
}Recover only from failures the boundary understands
Catching every Exception and labeling it not a number would hide failures unrelated to numeric grammar. Even Parse can fail for another reason, such as a numeric value outside decimal's range. Choose a handler or a TryParse-based validation path according to the input contract. Finally executes as ordinary managed control leaves the try/catch boundary, but forcibly terminating a browser worker with Stop is not a promise that managed cleanup finishes. Use deterministic ownership and cooperative cancellation where cleanup must be observed.
Add another record, then change the parsing contract
First append 4.25 to the prepared array. Predict the original four lines followed by ok 4.25 and checked. Hint: the handler belongs to one iteration, so the malformed middle record does not prevent a later iteration. Then move the try/catch outside the loop in a separate experiment and explain why a thrown exception exits the loop before reaching that later string. Restore the original recovery boundary after comparing the outcomes.
Next replace Parse with decimal.TryParse using an explicit invariant provider and a chosen NumberStyles value. Keep separate success and rejection messages, and decide where the checked message belongs when no exception is thrown for malformed input. Hint: inspect the returned boolean before using the out value; zero is also a legitimate parsed decimal. Test zero, a valid fractional value, malformed text, and a well-formed value too large for decimal. Describe which outcomes are ordinary input rejection rather than an unexpected defect.
Report the stage and the record that failed
Explain which statement throws for twelve, which statements are skipped, and why finally still runs in the normal handled path. Then identify the exact difference between a conversion failure and a business-rule rejection such as a negative price. A useful batch processor preserves enough context to identify the rejected record while keeping unexpected programming errors visible. Do not use a cleanup or completion message as a substitute for a success result.
- The first record prints a price and then checked.
- The second record prints its FormatException message and then checked.
- The unchanged sample has four output lines.
- The catch placement makes recovery local to each iteration.
A working example
foreach (var text in new[] { "12.50", "twelve" })
{
try
{
var price = decimal.Parse(text, System.Globalization.CultureInfo.InvariantCulture);
Console.WriteLine($"ok {price}");
}
catch (FormatException)
{
Console.WriteLine($"not a number: {text}");
}
finally
{
Console.WriteLine("checked");
}
}Expected output
ok 12.50 checked not a number: twelve checked
Read the output in pairs: a result, then checked.