Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Down the Rabbit Hole with Array of Generics

0.00/5 (No votes)
4 May 2016 2  
An Alice in Wonderland journey of generics, inverting object oriented programming, and generic type dispatching

The Problem

I recently had the odd requirement for creating an array of different generic types, in which I could call methods that would operate on the concrete type. Basically, what I wanted to accomplish would look sort of like this (non-working example):

public class Cat
{
  public void Meow() {Console.WriteLine("Meow");}
}

public class Dog
{
  public void Bark() {Console.WriteLine("Woof");}
}

public static class BasicExample
{
  static void Test()
  {
    object[] msgs = new object[]
    {
      new Cat(),
      new Dog(),
    };

    DoSomethingWith(msgs[0]);
    DoSomethingWith(msgs[1]);
  }

  static void DoSomethingWith(Cat cat)
  {
    cat.Meow();
  }

  static void DoSomethingWith(Dog dog)
  {
    dog.Bark();
  }
}

Ignore the fact that the usual way we implement the above example is through a common interface that implements something like Speak(). In my particular case, I needed an array of concrete types whose properties and methods vary, and where I could call an externally defined method that does some specific operation with the concrete type instance, qualified by some filtering on the properties of the instance.

The above code doesn't work because in DoSomethingWith(msgs[0]); the parameter, msgs[0] is of type object, not of the exact type initialized in the array. (For the advanced reader, my experiments with co- and contra-variance didn't lead anywhere.)

So I thought, let's do this with generics. All I need is an array of generics, like this:

public class Animal<T> { }
Animal<T>[] msgs = new Animal<T>[]
{
  new Animal<Cat>(),
  new Animal<Dog>(),
};

Oops, of course that doesn't work because the l-value Animal<T>[] msgs is not a known type -- T must be a defined type.

So a quick Google led me to an amusing post on Stack Overflow (here) where the response was: "It's not possible."

That was not acceptable!

Down the Rabbit Hole we Go!

The solution is rather simple -- we have to create a generic type (we already did, Animal<T>) but derive it from a non-generic type:

public abstract class Animal{ }

public class Animal<T> : Animal { }

Now, the array can be of type Animal :

Animal[] animals = new Animal[]
{
  new Animal<Cat>(),
  new Animal<Dog>(),
};

But the DoSomethingWith call is still not correct, because the type is now Animal.

The solution to this is to invert the call, so that DoSomethingWith is called by the generic Animal<T> class, because it's there that we know what type T is.

This requires an Action:

public class Animal<T> : Animal 
{
  public Action<T> Action { get; set; }
}

We now initialize the action when we construct the specific generic type:

Animal[] animals = new Animal[]
{
  new Animal<Cat>() {Action = (t) => DoSomethingWith(t)},
  new Animal<Dog>() {Action = (t) => DoSomethingWith(t)},
};

But how do we invoke the action? The secret sauce is in an abstract Call method in the non-generic Animal that is implemented in the generic Animal<T> class:

public abstract class Animal
{
  public abstract void Call(object animal);
}

public class Animal<T> : Animal 
{
  public Action<T> Action { get; set; }

  public override void Call(object animal)
  {
    Action((T)animal);
  }
}

Note also the cast to T in the call to Action!

Now, this works:

public static class BasicConcept3
{
  public static void Test()
  {
    Animal[] animals = new Animal[]
    {
      new Animal<Cat>() {Action = (t) => DoSomethingWith(t)},
      new Animal<Dog>() {Action = (t) => DoSomethingWith(t)},
    };

    animals[0].Call(new Cat());
  }

  static void DoSomethingWith(Cat cat)
  {
    cat.Meow();
  }

  static void DoSomethingWith(Dog dog)
  {
    dog.Bark();
  }
}

What's going on here?

 

But Wait, This is Stupid!

If I'm already instantiating Cat and Dog, why not just do this:

DoSomethingWith(new Cat());
DoSomethingWith(new Dog());

Because the code in the previous section was just an example to illustrate the implementation. The real goal here is to be able to receive an Animal, through, say, an event:

public class SpeakEventArgs : EventArgs
{
  public IAnimal Animal { get; set; }    // Could have been object Animal as well.
}

public static EventHandler<SpeakEventArgs> Speak;

Here, we are creating a separation of concerns -- the event receives some object, and we'll figure out how to route that object to the desired handler.

You'll notice that I snuck in an interface IAnimal. This isn't technically necessary, the property Animal could also simply be an object, but this ensures that we create the SpeakEventArgs with a type that implements the interface:

public class Cat : IAnimal
{
  public void Meow() { Console.WriteLine("Meow"); }
}

public class Dog : IAnimal
{
  public void Bark() { Console.WriteLine("Woof"); }
}

Now, our implementation requires a way to select the "route", which means that we also have to expose the type of T in order to qualify the route. Back to the generic and non-generic classes, where we add a property Type.

public abstract class Animal
{
  public abstract Type Type { get; }
  public abstract void Call(object animal);
}

public class Animal<T> : Animal
{
  public override Type Type { get { return typeof(T); } }
  public Action<T> Action { get; set; }

  public override void Call(object animal)
  {
    Action((T)animal);
  }
}

Now we can implement a dispatcher:

public static void OnSpeak(object sender, SpeakEventArgs args)
{
  animalRouter.Single(a => args.Animal.GetType() == a.Type).Call(args.Animal);
}

The full class looks like this now (all these statics are just a convenience, there's nothing preventing you from removing the static keywords and instantiating the class):

public static class EventsConcept
{
  public static EventHandler<SpeakEventArgs> Speak;

  private static Animal[] animalRouter = new Animal[]
  {
    new Animal<Cat>() {Action = (t) => DoSomethingWith(t)},
    new Animal<Dog>() {Action = (t) => DoSomethingWith(t)},
  };

  static EventsConcept()
  {
    Speak += OnSpeak; 
  }

  public static void OnSpeak(object sender, SpeakEventArgs args)
  {
    animalRouter.Single(a => args.Animal.GetType() == a.Type).Call(args.Animal);
  }

  static void DoSomethingWith(Cat cat)
  {
    cat.Meow();
  }

  static void DoSomethingWith(Dog dog)
  {
    dog.Bark();
  }
}

Somewhere else in the program, the event can now be fired:

EventsConcept.EventsConcept.Speak(null, new EventsConcept.SpeakEventArgs() 
{ Animal = new EventsConcept.Cat() });
EventsConcept.EventsConcept.Speak(null, new EventsConcept.SpeakEventArgs() 
{ Animal = new EventsConcept.Dog() });

Again you say, but this is stupid! I could just do:

EventsConcept.EventsConcept.DoSomethingWith(new EventsConcept.Cat());
EventsConcept.EventsConcept.DoSomethingWith(new EventsConcept.Dog());

Yes, of course, but that assumes that the publisher of the animal knows what to do with it. In the previous eventing implementation, we publish the animal, and it is the subscriber that figures out what to do.

What we've done here is turn object oriented programming on its head -- we're essentially implementing, via the dispatcher, what goes on behind the scenes in OOP dynamic dispatching.

A Better Example -- Filtering

What I really needed was a way to dispatch (aka route) the objects based on the values of one or more fields. For example:

public class Cat : IAnimal
{
  public bool IsSiamese {get; set;}
}

public class Dog : IAnimal
{
  public bool IsRotweiler {get;set;}
}

Notice that I also removed the Speak method, because we want the "computation" on the concrete Animal type to be decoupled from the Animal instance (as well as the publisher.)

This requires adding to our generic and non-generic instance management classes an abstract Where method and a Func<T> for implementing a filter expression:

public abstract class Animal
{
  public abstract Type Type { get; }
  public abstract void Call(object animal);
  public abstract bool Where(object animal);
}

public class Animal<T> : Animal
{
  public override Type Type { get { return typeof(T); } }
  public Action<T> Action { get; set; }
  public Func<T, bool> Filter { get; set; }

  public override void Call(object animal)
  {
    Action((T)animal);
  }

  public override bool Where(object animal)
  {
    return animal is T ? Filter((T)animal) : false;
  }
}

Notice that the implementation for Where also checks the type -- if we don't do this, we'll get a runtime error when executing the filter.

Our test class now looks like this:

public static class FilteredEventsConcept
{
  public static EventHandler<SpeakEventArgs> Speak;

  private static Animal[] animalRouter = new Animal[]
  {
    new Animal<Cat>() {Filter = (t) => t.IsSiamese, 
    Action = (t) => Console.WriteLine("Yowl!")},
    new Animal<Cat>() {Filter = (t) => !t.IsSiamese, 
    Action = (t) => Console.WriteLine("Meow")},
    new Animal<Dog>() {Filter = (t) => t.IsRotweiler, 
    Action = (t) => Console.WriteLine("Growl!")},
    new Animal<Dog>() {Filter = (t) => !t.IsRotweiler, 
    Action = (t) => Console.WriteLine("Woof")},
  };

  static FilteredEventsConcept()
  {
    Speak += OnSpeak;
  }

  public static void OnSpeak(object sender, SpeakEventArgs args)
  {
    animalRouter.Single(a => a.Where(args.Animal)).Call(args.Animal);
  }
}

and our test like this:

FilteredEventsConcept.TestCase.Test();

// So we stay in the same namespace and our test is easier to read:
public static class TestCase
{
  public static void Test()
  {
    FilteredEventsConcept.Speak(null, new SpeakEventArgs() { Animal = new Cat() });
    FilteredEventsConcept.Speak(null, new SpeakEventArgs() { Animal = new Cat() { IsSiamese = true } });
    FilteredEventsConcept.Speak(null, new SpeakEventArgs() { Animal = new Dog() });
    FilteredEventsConcept.Speak(null, new SpeakEventArgs() { Animal = new Dog() { IsRotweiler = true } });
  }
}

Now we've accomplished something useful -- our dispatcher not only dispatches based on type, but also allows us to qualify the action with some filter on the type instance's data. All this, simply to replace:

if (animal is Cat && ((Cat)animal).IsSiamese) Console.WriteLine("Yowl!");

etc. But I dislike using imperative code when there is a debatable, more complex, declarative solution!

Minor Refactoring

Because this is a generic router, we should really rename the two classes that support routing and change the animal parameter name simply obj:

public abstract class Route
{
  public abstract Type Type { get; }
  public abstract void Call(object obj);
  public abstract bool Where(object obj);
}

public class Route<T> : Route
{
  public override Type Type { get { return typeof(T); } }
  public Action<T> Action { get; set; }
  public Func<T, bool> Filter { get; set; }

  public override void Call(object obj)
  {
    Action((T)obj);
  }

  public override bool Where(object obj)
  {
    return obj is T ? Filter((T)obj) : false;
  }
}

Duck Typing

In Python, we can do something similar:

class Cat(object):
  def __init__(self):
    self.isSiamese = False

class Dog(object):
  def __init__(self):
    self.isRotweiler = False 

class Route(object):
  def __init__(self, typeCheck, filter, do):
    self.__typeCheck = typeCheck
    self.__filter = filter
    self.__do = do

  def where(self, obj):
    return self.__isType(obj) and self.__filter(obj)

  def do(self, obj):
    self.__do(obj)

  # Attributes and functions with a two leading underscore is the pythonic way of 
  # indicating the method is supposed to be "private", as this "scrambles" the name.
  def __isType(self, obj):
    return self.__typeCheck(obj)

  def __filter(self, obj):
    return self.__filter(obj)

router = [
  Route(lambda animal : type(animal) is Cat, 
  lambda animal : animal.isSiamese, lambda animal : speak("Yowl!")),
  Route(lambda animal : type(animal) is Cat, 
  lambda animal : not animal.isSiamese, lambda animal : speak("Meow")),
  Route(lambda animal : type(animal) is Dog, 
  lambda animal : animal.isRotweiler, lambda animal : speak("Growl!")),
  Route(lambda animal : type(animal) is Dog, 
  lambda animal : not animal.isRotweiler, lambda animal : speak("Woof"))
]
def speak(say):
  print(say)

def dispatcher(animal):
  filter(lambda route : route.where(animal), router)[0].do(animal)

cat1 = Cat()
cat2 = Cat()
cat2.isSiamese = True

dog1 = Dog()
dog2 = Dog()
dog2.isRotweiler = True

dispatcher(cat1)
dispatcher(cat2)
dispatcher(dog1)
dispatcher(dog2)

The salient point to the Python code is this:

router = [
  Route(lambda animal : type(animal) is Cat, 
	lambda animal : animal.isSiamese, lambda animal : speak("Yowl!")),
  Route(lambda animal : type(animal) is Cat, 
	lambda animal : not animal.isSiamese, lambda animal : speak("Meow")),
  Route(lambda animal : type(animal) is Dog, 
	lambda animal : animal.isRotweiler, lambda animal : speak("Growl!")),
  Route(lambda animal : type(animal) is Dog, 
	lambda animal : not animal.isRotweiler, lambda animal : speak("Woof"))
]

Here, the lambda expression lambda animal : animal.isSiamese is duck typed. It doesn't need to know the type at "compile time" (because there isn't any compile time) in order to evaluate the lambda expression. Conversely, in C#, intellisense already knows the type because t can only be of type Cat:

The only way in Python to know that you haven't screwed up:

Route(lambda animal : type(animal) is Cat, 
lambda animal : animal.isRotweiler, lambda animal : speak("Yowl!")),

is to run (or preferably write a unit test) the code:

C# Duck Typing

Technically, we have the same problem with our C# code. Consider this simpler example:

public abstract class SomeMessage
{
  public abstract void Call(object withMessage);
}

public class Message<T> : SomeMessage
{
  public Action<T> Action { get; set; }

  public override void Call(object withMessage)
  {
    Action((T)withMessage);
  }
}

public class CallbackExamplesProgram
{
  public static void Test()
  {
    SomeMessage[] messages = new SomeMessage[]
    {
      new Message<MessageA>() {Action = (msg) => MessageCallback(msg)},
      new Message<MessageB>() {Action = (msg) => MessageCallback(msg)}
    };

  try
  {
    // Cast error caught at runtime:
    messages[0].Call(new MessageB());
  }
  catch (Exception ex)
  {
    Console.WriteLine(ex.Message);
  }
}

Here, we calling the Action for the first message of type MessageA but passing in a MessageB instance. We get, at runtime (not at compile time!):

which is why, in the filtered event example earlier, the Where method checks the type:

public override bool Where(object obj)
{
  return obj is T ? Filter((T)obj) : false;
}

Visitor Pattern

As ND Hung pointed out in the comments, the Visitor Pattern is good OOP solution to this problem.  A bare bones example would look like this:

namespace VisitorPatternExample
{
  public interface IVisitor
  {
    void Visit(Cat animal);
    void Visit(Dog animal);
  }

  public interface IAnimal
  {
    void Accept(IVisitor visitor);
  }

  public class Cat : IAnimal
  {
    public void Accept(IVisitor visitor)
    {
      visitor.Visit(this);
    }
  }

  public class Dog : IAnimal
  {
    public void Accept(IVisitor visitor)
    {
      visitor.Visit(this);
    }
  }

  public class Visitor : IVisitor
  {
    public void Visit(Cat cat)
    {
      Console.WriteLine("Cat");
    }

    public void Visit(Dog dog)
    {
      Console.WriteLine("Dog");
    }
  }

  public static class VisitorTest
  {
    public static void Test()
    {
      IAnimal cat = new Cat();
      IAnimal dog = new Dog();

      Visitor visitor = new Visitor();
      cat.Accept(visitor);
      dog.Accept(visitor);
    }
  }
}

Declarative vs. Imperative, and Use Case Analysis

The visitor pattern avoids the whole issue with array of generics, which is what I was aiming to solve.  However, this points out that we may think that the use-case (at least in my examples) requires an array of generics, but in reality, something as simple as the visitor pattern can solve the problem without turning OOP on its head and creating complex solutions with generic arrays.  It also reveals the difference (and complexity that one gets into) with declarative vs. imperative code.  With the visitor pattern, the filtering must be implemented imperatively:

public void Visit(Cat cat)
{
  if (cat.IsSiamese)
    Console.WriteLine("Yowl!");
  else
    Console.WriteLine("Meow.");
}

public void Visit(Dog dog)
{
  if (dog.IsRotweiler)
    Console.WriteLine("Growl!");
  else
    Console.WriteLine("Woof");
  }
}

This of course is a perfectly acceptable solution.  Conversely, with the declarative solution, we don't need an IVisitor (or similar) interface, so when new message types are added, only the declarative router is changed.  In the visitor pattern, the interface must be touched to add a Visit method for the new type.  The tradeoff is complexity in implementing a declarative approach vs. the simplicity of the imperative approach.

Conclusion

This was originally intended to be posted as a tip, but it grew a bit too large!  The visitor pattern doesn't demonstrate how to work with an array of generics (which was the point of this article) but it is a good example of how design patterns can be used to solve the use case (which after all doesn't require an array of generics) and the tradeoffs between imperative and declarative programming..

The source code contains the examples in this article plus a few more.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here