C# from Basics to Mastery — Part 7
Managing resource lifetime with exceptions and IDisposable
The articles written on this blog were created with the goal of teaching. Also, the information in the article may be incorrect, so please be careful.
Code that can fail must be designed to include the state after failure. It is important not only to catch exceptions, but also to ensure that resources such as files and connections are organized in any path.
predictable failure
bool TrySpendGold(int amount)
{
if (amount < 0 || Gold < amount)
{
return false;
}
Gold -= amount;
return true;
}
Frequently occurring results, such as lack of gold, can be expressed as bool or a separate result type. Exceptions are used for failures that are difficult to handle normally in the current flow, such as file corruption or contract violation.
Exception handling
try
{
string json = File.ReadAllText(path);
settings = JsonSerializer.Deserialize<Settings>(json)
?? throw new InvalidDataException("Settings are empty");
}
catch (IOException error)
{
logger.Log(error.Message);
settings = Settings.CreateDefault();
}
Only catch exceptions that can be handled and either recover or pass them up with more meaningful information. If you catch all exceptions without handling their content, bugs can hide quietly.
IDisposable and using
using FileStream stream = File.OpenRead(path);
using var reader = new StreamReader(stream);
string text = reader.ReadToEnd();
When it goes out of the using scope, Dispose is called. In addition to normal returns, file handles are cleaned up from the path where an exception occurred.
GC reclaims memory for managed objects, but does not promise to immediately close files, sockets, or native buffers. Types that own these resources must provide an IDisposable contract.
Replacing states later
Settings LoadSettings(string path)
{
string json = File.ReadAllText(path);
return JsonSerializer.Deserialize<Settings>(json)
?? throw new InvalidDataException("Invalid settings");
}
Settings loaded = LoadSettings(path);
currentSettings = loaded;
If you completely create a new setting and replace it with the existing setting, the existing state will remain even if there is a failure during loading. Exception handling is about making the state and resource lifespan before and after failure more predictable than the number of catches.