たくさんの回答ありがとうございます。

「i.MoveNext()により、実際にGetEnumerator()が呼ばれる」と思った理由は、>>253が書いてくれてるように、
デバッガでステップ実行すると、MoveNext()を呼んだタイミングでGetEnumerator()の中に入っていくためです。

皆のおかげで、その考えが誤りであることがわかりました。

ただ、
IEnumerator i = collection.GetEnumerator(); // @
i.MoveNext() // A
については、まだイマイチ自信がないんですが…。

全員のレスをつなぎ合わせて、下記のように理解したのですが、あってますでしょうか?

@で、
・GetEnumerator()により、ジェネリック版IEnumerator<T>を実装したクラスのインスタンスが生成される
・そのクラスは、ジェネリック用と非ジェネリック用の2つのMoveNext()を持つ
 (IEnumerator<T>はIEnumeratorを継承しているため、両方のMoveNext()が作られる)
・ジェネリック用のMoveNext()の内容は、ジェネリック版のyield returnブロックから生成される
・非ジェネリック用のMoveNext()は、ジェネリック版のMoveNext()のラッパーである

Aで、
・iは非ジェネリックのIEnumeratorなので、非ジェネリック用のMoveNext()が呼ばれる
・それにより、ラップされているジェネリック用のMoveNext()が呼ばれ、結果として、
 「ジェネリック版のyield returnブロックから生成された処理」が呼ばれる。

以上、よろしくお願いします。