foreach doesn’t use pattern matching to invoke Dispose().
Paulo Morgado

Exactly and that’s the purpose of the first implementation. If there’s nothing to dispose, there should not be a try/finally block. You can see in SharpLab that no try/finally block is added.

When implementing IEnumerable<T>, it forces the enumerator to implement IDisposable, because IEnumerator<T> derives from it. You can play with the code in SharpLab and you’ll find that there’s no way to get rid of it. In ‘JIT Asm’ mode, you can see that Dispose() is being called (no JIT optimisation occurred). In ‘IL’ mode, you can see that the call is ‘constrained’ to the enumerator type, making it a direct call even after casted to IDisposable.