Проверенный downcast, используемый в идиомах Linq, совершенно безопасен.
foreach (Monkey m in myAnimals.OfType<Monkey>())
Program.FeedAnimal(m);
Или вы можете использовать шаблон посетителя. Объект посетитель знает все типы животных, поэтому у него есть функция FeedAnimal
для каждого. Вы передаете объект посетителя функции Feed
животного, и он вызывает правильный метод FeedAnimal
, передавая this
.
Чтобы сделать его расширяемым, вам понадобится Dictionary
кормушек для животных:
private static Dictionary<Type, Action<Animal>> _feeders;
Чтобы зарегистрировать действие кормления, вы должны начать с:
_feeders[typeof(Monkey)] =
a =>
{
Monkey m = (Monkey)a;
// give food to m somehow
};
Но есть уныние, и вы также должны указать правильный тип ключа. Так что сделайте помощника:
public static void AddFeeder<TAnimal>(Action<TAnimal> feeder) where TAnimal : Animal
{
_feeders[typeof(TAnimal)] = a => feeder((TAnimal)a);
}
Это захватывает образец, так что вы можете использовать его полностью безопасно:
AddFeeder<Monkey>(monkey => GiveBananasTo(monkey));
AddFeeder<Dog>(dog => ThrowBiscuitsAt(dog));
Тогда, когда вам нужно накормить животное, используйте этот метод расширения:
public static void Feed(this Animal a)
{
_feeders[a.GetType()](a);
}
, например
a.Feed();
Таким образом, _feeders будет частным статическим полем в том же статическом классе, что и метод расширения, вместе с методом AddFeeder
.
Обновление: весь код в одном месте, также с поддержкой наследования:
public static class AnimalFeeding
{
private static Dictionary<Type, Action<Animal>> _feeders
= new Dictionary<Type, Action<Animal>>();
public static void AddFeeder<TAnimal>(Action<TAnimal> feeder)
where TAnimal : Animal
{
_feeders[typeof(TAnimal)] = a => feeder((TAnimal)a);
}
public static void Feed(this Animal a)
{
for (Type t = a.GetType(); t != null; t = t.BaseType)
{
Action<Animal> feeder;
if (_feeders.TryGetValue(t, out feeder))
{
feeder(a);
return;
}
}
throw new SystemException("No feeder found for " + a.GetType());
}
}
Вы также можете получить цикл поддержки интерфейсов для каждого типа t
- в основном та же идея, поэтому я сохранил простой пример здесь.