La differenza effettiva tra i tuoi esempi è trascurabile purché non vengano generate eccezioni.
Se, tuttavia, viene generata un'eccezione nella clausola 'try', il primo esempio la inghiottirà completamente. Il secondo esempio solleverà l'eccezione al passaggio successivo dello stack di chiamate, quindi la differenza negli esempi dichiarati è che uno oscura completamente qualsiasi eccezione (primo esempio) e l'altro (secondo esempio) conserva le informazioni sull'eccezione per una potenziale gestione successiva mentre sta ancora eseguendo il contenuto nella clausola 'finally'.
Se, ad esempio, dovessi inserire il codice nella clausola "catch" del primo esempio che ha generato un'eccezione (o quella inizialmente sollevata o nuova), il codice di pulizia del lettore non verrebbe mai eseguito. Viene infine eseguito indipendentemente da ciò che accade nella clausola "catch".
Quindi, la principale differenza tra 'catch' e 'finally' è che i contenuti del blocco 'finally' (con alcune rare eccezioni) possono essere considerati garantiti per l'esecuzione, anche di fronte a un'eccezione imprevista, mentre qualsiasi codice che segue una clausola di "cattura" (ma al di fuori di una clausola "finalmente") non avrebbe tale garanzia.
Per inciso, Stream e StreamReader implementano entrambi IDisposable e possono essere racchiusi in un blocco "using". I blocchi 'using' sono l'equivalente semantico di try / finally (no 'catch'), quindi il tuo esempio potrebbe essere espresso più tersamente come:
using (StreamReader reader = new StreamReader("myfile.txt"))
{
int i = 5 / 0;
}
... che chiuderà e eliminerà l'istanza StreamReader quando non rientra nell'ambito. Spero che questo ti aiuti.