Ну, видимо, потому что сигнатура метода base.ToString() уже находится в таблице виртуальных функций, и он вызывал бы сам себя при обращении к этому методу, потому что в коде, который вызывает base.ToString(), уже очевидно, что сигнатура вызова совпадает с сигнатурой базового класса (переопределен метод ToString()), тогда, нашелся бы вызов метода дочернего класса callvirt, то есть собственно, исполняемый перегуженный в дочернем классе метод, и он вызвал бы сам себя, вместо простого вызова call без поиска по таблице сигнатур виртуальных методов (переопределяемых).