Thursday, February 22, 2007 4:36 PM
I've often wanted to do the following in code...
List<Animal> myAnimals = new List<Animal>();
myAnimals.Add(new Dog("Spot"));
myAnimals.Add(new Cat("Fluffy"));
myAnimals.Add(new Dog("Rover"));
foreach (Dog myDog in myAnimals)
{
PetTheDog(myDog);
}
Basically, I want to have an enumerable collection of base class instances that I can just loop through, act on the types I want, and then just disregard the rest. Seems like a small request, right? Seems logical. Heck, it even seems like it should even work. I mean, foreach is an abstraction anyway, right? Why can't it just do this for me? I even tried it once just to make sure it really doesn't work.
It doesn't.
Everything's fine until you hit "Fluffy". Then the runtime tries to cast Fluffy to a Dog...but Fluffy is a Cat, Fluffy doesn't want to be a Dog. So we get an InvalidCastException and the whole thing falls down right in front of us. I've played around with this off and on for awhile now and I've never been able to come up with a way to do it. This has always been a thorn in my side because I create lists made up of base classes all the time in which in a few key spots must have their elements handled differently depending on what type of object they are. This would be a great use for this but instead I always end up with the following:
foreach (Dog myDog in myAnimals)
{
if (myDog is Dog)
{
PetTheDog(myDog);
}
}
Don't get me wrong, this works fine. It's never failed me, it just always seemed like it was more verbose than it needed to be. It wasn't perfect but I could live with it I guess...until today. Today, I happened on this amazing post by James Curran over at Honest Illusion. As soon as I saw this, I knew that my dream was possible...and even better, James had solved all of the hard problems for me!
Borrowing heavily from Jame's example, I was able to create a class called GetObjectsOfType which would create an enumerator which would decide what to return based on the instance I asked for. I've included the entire code for download here so I'll just show the meat and potatoes method of GetEnumerator() and the constructor which sets our collection below:
public GetObjectsOfType(IEnumerable<BASE> enumerable)
{
_enumerable = enumerable;
}
public IEnumerator<DERIVED> GetEnumerator()
{
// Get the enumerator from our original collection
IEnumerator<BASE> iterator = _enumerable.GetEnumerator();
// Loop through all elements of the collection
while (iterator.MoveNext())
{
// If the current element is of the type we desire then return
// it, otherwise just skip it
if (iterator.Current is DERIVED)
{
yield return ((DERIVED)iterator.Current);
}
}
}
Basically I'm just creating an enumerator from the enumerable collection which is passed into our collection. Then, in standard iterator fashion, I'm just looping through the collection and allowing us to return an iterator to it only if it is of type DERIVED. What are BASE and DERIVED? Well those are my generic types that I've created this class with as you can see from the class signature below:
public class GetObjectsOfType<BASE, DERIVED> : IEnumerable<DERIVED>
where DERIVED : BASE
{
. . .
}
Basically, I'm asking for two types when GetObjectsOfType is created: BASE and DERIVED. BASE is the common type shared by all elements of the collection, which in our initial example is Animal. DERIVED represents the type that we're actually interested in retrieving, which was Dog in our initial example. All objects which are not of type Dog are simply ignored and not returned to our foreach loop. Note that GetObjectsOfType itself implements IEnumerable<DERIVED> which allows us to use it implicitly in the foreach loop. Also note that I've added the constraint that DERIVED must derive from BASE. In actuality this isn't really necessary, I've just added it to try and enforce the expected relationship between BASE and DERIVED. In practice this may actually place an unnecessary restriction on the situations where you can use this class and you would likely gain some flexibility by removing it. As previously stated, I've really only included it here for elegance of design and illustrative purposes.
So, now that we have our class, how does this work in practice? Well, let's take a look...
// Create a list of Animals and fill it with both Dogs and Cats
List<Animal> animals = new List<Animal>();
animals.Add(new Cat("Fluffy"));
animals.Add(new Dog("Spot"));
animals.Add(new Dog("Lucky"));
animals.Add(new Cat("Frisky"));
animals.Add(new Dog("Fido"));
// Now loop through the list, but only retrieve the Dogs
foreach (Dog dog in
new GetObjectsOfType<Animal, Dog>(animals))
{
Console.WriteLine("Found a dog named " + dog.Name);
}
Here I've created a list of Animals containing both Dogs and Cats, but I'm only interested in working with the Dogs. Well, I can simply create an instance of our GetObjectsOfType class inline in the foreach loop, tell it that I want to retrieve all instances of type Dog from my collection of Animals, and then just hand it the collection I want it to work with. That's it! The only elements that make it into the loop are elements of type Dog. No more messy if statements, no more casting inline, everything is taken care of immediately. Let's try it again with another collection...
// Create a list of Students and fill it with both MaleStudents
// and FemaleStudents
List<Student> students = new List<Student>();
students.Add(new FemaleStudent("Mary"));
students.Add(new MaleStudent("Jim"));
students.Add(new FemaleStudent("Betty"));
students.Add(new MaleStudent("Michael"));
students.Add(new FemaleStudent("Susan"));
// Now loop through the list, but only retrieve the FemaleStudents
foreach (FemaleStudent femaleStudent in
new GetObjectsOfType<Student, FemaleStudent>(students))
{
Console.WriteLine("Found a female student named " +
femaleStudent.Name);
}
In this case I'm doing the same thing, except with MaleStudents and FemaleStudents. I simply tell my class what types of objects I'm looking for, what types are in the list, and what collection to work from. Everything else is taken care of automagically!
OK, ok...so this works great for custom classes. What about other types like structs or primitives? Well, everything derives from object...right? The example below shows you how can apply this to standard types which aren't from my contrived examples...
// Create a list of objects and fill it with all kinds of different
// types.
List<object> objects = new List<object>();
objects.Add("This is a string");
objects.Add(34);
objects.Add(100.0F);
objects.Add(56);
objects.Add(79.0);
// Now loop through the list, but only retrieve the integers
foreach (int integer in
new GetObjectsOfType<object, int>(objects))
{
Console.WriteLine("Found an integer with a value of " +
integer.ToString(Thread.CurrentThread.CurrentUICulture));
}
In this case I'm simply creating a whole array of objects and filling it with all sorts of different types, but when the time comes all I want to work with are the integers. Does it work? You bet!
That's enough of my little examples. Grab the code and give it a shot yourself. I hope your as excited as me about this, I'm already thinking of dozens of places in my current codebase that I can go back and plug this in. I hope you have some places you can use it as well, and if you do leave a note in the comments and tell me about it!
Once again, I want to thank James Curran at Honest Illusion for the inspiration for this post. Great work, James!