If you are using C # 4.0, you can ask yourself if the processor should return an IEnumerable<T> , not an IList<T> . If the answer is yes, then you can profit from covariance:
abstract class AnimalProcessor { public abstract IEnumerable<Animal> ProcessResults(); } class GiraffeProcessor : AnimalProcessor { public override IEnumerable<Animal> ProcessResults() { return new List<Giraffe>(); } } class LionProcessor : AnimalProcessor { public override IEnumerable<Animal> ProcessResults() { return new List<Lion>(); } }
You have several advantages here. First, you can implement them as iterator blocks:
class GiraffeProcessor : AnimalProcessor { public override IEnumerable<Animal> ProcessResults() { yield break; } }
Secondly, and less trivially, you allow the client code to decide what collection to collect for dropping animals - if any. For example, think that a consumer might wish a LinkedList<Animal> :
var animals = new LinkedList<Animal>(animalProcessor.ProcessResults());
Or think that the client may only need to repeat the sequence:
foreach (var animal in animalProcessor.ProcessResults()) { }
In any case, if you used the ToList() call in ProcessResults, you created a list for nothing. If the consumer really wants a List<Animal> , this can be done very easily:
var animals = new List<Animal>(animalProcessor.ProcessResults());
Finally, you can also take the general approach, even if you change the interface type of the method return value:
abstract class AnimalProcessor<T> where T : Animal { public abstract IEnumerable<T> ProcessResults(); } class GiraffeProcessor : AnimalProcessor<Giraffe> { public override IEnumerable<Giraffe> ProcessResults() { yield break; } } class LionProcessor : AnimalProcessor<Lion> { public override IEnumerable<Lion> ProcessResults() { return Enumerable.Empty<Lion>(); } }