Потому что, если поведение по умолчанию - ничего не делать, но производные классы могут захотеть что-то сделать.Это совершенно правильная структура.
Это позволяет вашему базовому коду вызывать ее.Вы склонны видеть похожие конструкции, когда есть код «BeforeXXX» и «AfterXXX», в базовом классе этот код пуст, но метод должен быть там для компиляции.В производных классах этот код является необязательным, но для его переопределения он должен быть виртуальным.
Тот факт, что он находится в абстрактном классе, не должен путать его поведение.
Пример:
abstract class Base
{
public void ProcessMessages(IMessage[] messages)
{
PreProcess(messages);
// Process.
PostProcess(messages);
}
public virtual void PreProcess(IMessage[] messages)
{
// Base class does nothing.
}
public virtual void PostProcess(IMessage[] messages)
{
// Base class does nothing.
}
}
class Derived : Base
{
public override void PostProcess(IMessage[] messages)
{
// Do something, log or whatever.
}
// Don't want to bother with pre-process.
}
Если бы эти методы (Pre, Post) были абстрактными, то все производные классы нуждались бы для их реализации (вероятно, в виде пустых методов) - мусор кода, который можно удалить с помощью пустого виртуальногометоды на базе.