I have a abstract, custom collection type we'lll call AnimalCollection
for storing a collection of Animal
objects. Animals that need to be stored in an animal collection are so special that they get their own implementation of AnimalCollection
for storing additional information specific to that type of collection, e.g. TurtleCollection
, GiraffeCollection
, etc.
Since we're all collections here, I think it makes sense that AnimalCollection
should implement IEnumerable<Animal>
so users can iterate over it. However, in the case of our more derived classes it makes more sense to have implemented IEnumerable<Turtle>
, IEnumerable<Giraffe>
, etc.
class Animal {}
class Turtle : Animal {}
abstract class AnimalCollection : IEnumerable<Animal>
{
}
class TurtleCollection : AnimalCollection, IEnumerable<Turtle>
{
}
That way, when someone does foreach (var turtle in turtleCollection)
var
is automatically of type Turtle
. Without having implemented IEnumerable<Turtle>
, the user would have had to type foreach (Turtle turtle in turtleCollection)
. Since I always type var
everywhere I can, I'm sure I would eventually trip over the fact my turtle
variable wasn't casted to the type I expected, which means the users of my API would probably stumble over this as well!
So while I'm aware that having a type implement IEnumerable<T>
on two adjacent types in an inheritance tree can cause issues, I'm wondering whether there could be hidden gotchas in using multiple IEnumerable<T>
with types in the same inheritance hierarchy?
EDIT: The most sensible solution here would be to have AnimalCollection<T>
and then define TurtleCollection : AnimalCollection<Turtle>
as has been pointed out by some of the existing comments/answers. However the reason my AnimalCollection
is non-generic is that the type I'm calling AnimalCollection
actually represents a node in a tree. In actuality, I have types like
class Node {}
class FooNode : Node {}
class BarNode : Node {}
class NodeCollection : Node {}
class FooNodeCollection : NodeCollection {}
class BarNodeCollection : NodeCollection {}
FooNodeCollection
is constrained in its constructor to only take objects of type FooNode
. I then have visitors for accessing the nodes of a tree
class NodeVisitor
{
abstract void VisitFoo(FooNode foo);
abstract void VisitBar(BarNode bar);
abstract void VisitCollection(NodeCollection collection);
}
The exact type of node that is contained in the NodeCollection
doesn't really matter as far as visitors are concerned; all the visitor will do is simply iterate over its children and have them dispatch themselves to the visitor.
Potentially one solution then could be to have two types - NodeCollection
which is used by the visitor, and NodeCollection<T>
which derives from NodeCollection
and is the one that actually implements IEnumerable<T>
, however I'm also interested to know what types of issues could arise from utilizing multiple IEnumerable<T>
in the way originally stated