Abstract Classes and Generic Methods

I ran into a fun scenario where I passing an object that derived from an abstract base class into a generic static method where it would be passed on to another generic method for some special, type-specific processing. Everything worked great when I passed in an object declared as one of the derived types. However, when the object was declared as the abstract base type, it all fell apart because the generic method wanted to treat the object as the base type and not the actual, derived type!

Problem

Consider this re-invented version of the scenario. I have a static PatientRouter class that accepts injured Creatures. If the Creature is a Human, it will be routed to a HumanHospital. If it’s an Animal, it will be routed to an AnimalHospital. Note, however, that the code fails if a Creature object is received, even if the Creature is actually a Human or Animal. We need to do something so this Creature can be correctly cared for!

public static class PatientRouter
{
    public static void Route<T>(T creature) 
        where T : Creature
    {
        SendToHospital(creature);
    }

    public static void SendToHospital<T>(T creature)
        where T : Creature
    {
        if (typeof(T) == typeof(Creature))
        {
            throw new Exception("Unacceptable!");
        }
        if (typeof(T) == typeof(Human))
        {
            var h = new HumanHospital();
            h.CareFor(creature as Human);
        }
        if (typeof(T) == typeof(Animal))
        {
            var h = new AnimalHospital();
            h.CareFor(creature as Animal);
        }
    }
}

Solution

There are two options that I found for dealing with this scenario. The first is to use the dynamic keyword introduced in .NET Framework 4.0. Here’s what that might look like:

public static void Route<T>(T creature)
    where T : Creature
{
    dynamic d = creature;
    SendToHospital(d);
}

Unfortunately, .NET 4.0 wasn’t an option for me, though. I was, however, able to come up with an acceptable solution using reflection. I don’t love using reflection to execute a method like this, but it gets the job done–so I’m content to use it in a scenario like this until .NET 4.0 becomes available.

public static void Route<T>(T creature) 
    where T : Creature
{
    // using reflection
    typeof(PatientRouter)
        .GetMethod("SendToHospital")
        .MakeGenericMethod(creature.GetType())
        .Invoke(null, new[] { creature });
}

For reference, here’s the complete example with both solutions:

namespace samples.BaseClassGenericMethod
{
    using System;

    public abstract class Creature
    {
        public bool IsInjured { get; set; }
    }

    public class Human : Creature
    {
        /* human stuff */
    }

    public class Animal : Creature
    {
        /* animal stuff */
    }

    public interface IHospital<T> where T : Creature
    {
        void CareFor(T patient);
    }

    public class HumanHospital : IHospital<Human>
    {
        public void CareFor(Human patient)
        {
            Console.WriteLine("Caring for human!");
        }
    }

    public class AnimalHospital : IHospital<Animal>
    {
        public void CareFor(Animal patient)
        {
            Console.WriteLine("Caring for animal!");
        }
    }

    public static class PatientRouter
    {
        public static void Route<T>(T creature) 
            where T : Creature
        {
            // base case
            SendToHospital(creature);

            // using dynamic (.NET 4.0)
            //dynamic d = creature;
            //SendToHospital(d);

            // using reflection
            //var h = typeof(PatientRouter)
            //    .GetMethod("SendToHospital")
            //    .MakeGenericMethod(creature.GetType())
            //    .Invoke(null, new[] { creature });
        }

        public static void SendToHospital<T>(T creature)
            where T : Creature
        {
            if (typeof(T) == typeof(Creature))
            {
                throw new Exception("Unacceptable!");
            }
            if (typeof(T) == typeof(Human))
            {
                var h = new HumanHospital();
                h.CareFor(creature as Human);
            }
            if (typeof(T) == typeof(Animal))
            {
                var h = new AnimalHospital();
                h.CareFor(creature as Animal);
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var h = new Human();
            PatientRouter.Route(h);

            var a = new Animal();
            PatientRouter.Route(a);

            Creature c = new Human();
            PatientRouter.Route(c);

            Console.ReadLine();
        }
    }
}
Advertisements

Leave a comment

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s