Если простыми словами - то обычные методы лежат в памяти по заранее известному месту, и компилятор генерирует инструкцию "вызвать код по адресу XXX".
Вызываемый код всегда один и тот же.
с виртуальными - это зависит от класса, поэтому компилятор генерирует такие инструкции:
"посмотреть что там за класс у obj, сходить в таблицу методов этого класса, найти там адрес для функции ToString, и вызвать код по этому адресу"
для одного класса адрес будет XXX, и вызовется один код, для другого класса будет YYY и вызовется другой код.