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.