Функция, которая принимает базовый класс в качестве (не ссылочного) параметра, может быть преобразована в делегат, который принимает производный класс в качестве параметра. Это связано с тем, что функция, принимающая base, может быть безопасно заменена везде, где используется функция, которая принимает производный класс.
void TakesObject(object o)
{
...
}
Action<string> myAction=TakesObject;
Вы можете вызвать myAction, только передав строку. И поскольку каждая строка является объектом, контракт для TakesObject выполняется.
В вашем случае это работает, потому что каждый AlarmEventArgs
тоже EventArgs
. Таким образом, требования контракта вашего обработчика событий менее строги, чем контрактные гарантии для типа делегата, используемого событием.
Это называется со-и противо-дисперсией. У вас есть ко-дисперсия в типах возвращаемых данных и противоположная дисперсия в параметрах.
Проверьте эту статью на MSDN:
Использование дисперсии в делегатах (C # и Visual Basic)