IDisposable is typically used for memory cleanup, but it’s actually a pattern for any paired start/end operation. The using statement calls Dispose() automatically when the block exits; even on exceptions or early returns.
This means temporary state changes become self-contained. The setup and teardown are declared together, so you can’t forget the cleanup. For example:
- Locking input during cutscenes: lock in constructor, unlock in
Dispose() - Freezing time for pause menus: store previous timescale, restore it on dispose
- Disabling UI during transitions: block raycasts on entry, re-enable on exit
Victory Sequence Example
Victory sequences often need to lock inputs, freeze time, disable UI, then undo all of it. The naive approach is error-prone:
void OnPlayerWin()
{
inputManager.Lock();
uiCanvas.blocksRaycasts = false;
Time.timeScale = 0f;
PlayVictoryEffects();
// Easy to forget, reorder wrong, or miss in early returns
Time.timeScale = 1f;
uiCanvas.blocksRaycasts = true;
inputManager.Unlock();
}Problems: Hard to read the “real” logic, easy to mess up the cleanup order, breaks if someone adds an early return.
Create reusable disposable helpers:
public class InputLock : IDisposable
{
private readonly InputManager _input;
public InputLock(InputManager input) { _input = input; _input.Lock(); }
public void Dispose() => _input.Unlock();
}
public class FreezeTime : IDisposable
{
private readonly float _previousScale;
public FreezeTime() { _previousScale = Time.timeScale; Time.timeScale = 0f; }
public void Dispose() => Time.timeScale = _previousScale;
}
public class BlockUI : IDisposable
{
private readonly CanvasGroup _canvas;
public BlockUI(CanvasGroup canvas) { _canvas = canvas; _canvas.blocksRaycasts = false; }
public void Dispose() => _canvas.blocksRaycasts = true;
}Now the victory code is clean:
void OnPlayerWin()
{
using (new InputLock(inputManager))
using (new BlockUI(uiCanvas))
using (new FreezeTime())
{
PlayVictoryEffects();
}
}The magic: LIFO cleanup order is enforced automatically (time unfreezes first, then UI unblocks, then input unlocks). Early returns or exceptions still trigger dispose. The intent is immediately clear; setup is “secondary,” the body is the real work.