Posts
208
Comments
1144
Trackbacks
51
Limitations of Generics Polymorphism

This content has moved - click here.

 

With the advent of .NET 2.0, Generics is undoubtedly the single most important language enhancement.  However, to use generics to the full potential, developers should understand both the capabilities and limitations of generics as they relate specifically to polymorphism.  In short, while generics do support some polymorphic behavior, the use of Interfaces should still be the preferred polymorphic mechanism in many cases.

First, an example of polymorphic capabilities,

abstract class Person

{

    public string FirstName;

    public string LastName;

    public void Speak();

}

class Fireman : Person

{

    public int NumFiresExtinguished;

}

class Policeman : Person

{

    public int NumArrestsMade;

}

Using System.Collections.Generics.List<>, one could consume like this:

List<Person> list = new List<Person>();

list.Add(new Fireman());

list.Add(new Policeman());

Since both Fireman and Policeman inherit from Person, this compiles just fine.

Futher, with constraints we can define our own generic sub-classes:

class PersonAction where T : Person

{

    public void DoAction(T item);

}

Like the example above, we can consume like this:

PersonAction<Person> personAction = new PersonAction<Person>();

personAction.DoAction(new Fireman());

personAction.DoAction(new Policeman());

PersonAction<Fireman> fireAction = new PersonAction<Fireman>();

fireAction.DoAction(new Fireman());

//fireAction.DoAction(new Policeman()); <--Does not compile which is good

So far, everything is as we expect.  However, if we try to push generic polymorphic behavior one step further, this is where things gets interesting.

List<PersonAction<Person>> personActionList = new List<PersonAction<Person>>();

//personActionList.Add(new PersonAction()); <--Does not compile

//personActionList.Add(new PersonAction()); <--Does not compile

One would think that these two previous statements would be legal.  After all, we specified that the PersonAction must be and PersonAction certainly seems to satisfy this at first glance.  However, the compiler does not allow this because when we use generics in this way, the compiler is expecting the exact type – Person.

The good news is that this situation is easily rectified by using Interfaces.

interface IAction

{

    void DoAction(Person item);

}

And we’ll change PersonAction to:

class PersonAction : IAction where T : Person

{

    public void DoAction(T item);

    public void DoAction(Person item)

    {  this.DoAction((T)item); }

}

Now we can simply do this for polymorphic behavior with generic classes:

List<IAction> personActionList2 = new List<IAction>();

personActionList2.Add(new PersonAction<Fireman>());

personActionList2.Add(new PersonAction<Policeman>());

While there is no doubt that generics is one of the most important language enhancements we’ve seen in a while, there are many situation where traditional OOP with Interfaces should be the preferred implementation to support generics.

posted on Monday, December 5, 2005 6:01 PM Print
Comments
Gravatar
# re: Limitations of Generics Polymorphism
Random Reader
12/5/2005 10:12 PM
Actually it does make sense to me that they're illegal. The key point is that a generic class is a type itself. The compiler doesn't care about Fireman vs Person, it cares about PersonAction<Fireman> vs PersonAction<Person>. In other words, it needs a relationship between the two PersonAction types, not the generic type parameters that created them.

One way to express this:

abstract class PersonAction
{
}

class PersonAction<T> : PersonAction where T : Person
{
public void DoAction(T item) { }
}

List<PersonAction> personActionList = new List<PersonAction>();
Gravatar
# re: Limitations of Generics Polymorphism
michelotti
12/6/2005 4:11 AM
Thanks for your replay. Actually, the code you posted above illustrates my example perfectly. In order to get it to compile, you had to use a NON-generic class. And if with that you get no polymorphic behavior - the abstract PersonAction class has no methods. In this case, there'd be no way to get at the DoAction() method without casting. Again, it's the difference in polymorphism with generic versus non-generic classes.
Gravatar
# re: Limitations of Generics Polymorphism
Random Reader
12/6/2005 3:30 PM
Ah, yes, now I see your point about the polymorphism (or lack of it). I don't think I've seen that angle discussed anywhere before.

The basic issue for DoAction() is trying to provide a virtual override with a different type argument. That's not possible in non-generic polymorphism either. A working case with the base class would be identical to the interface case.

If the override issue was somehow solved, and a generic class implicitly inherited versions of itself parameterized on the base classes of its own type parameters (there's a wordy beginning), then PersonAction<Fireman> would be derived from PersonAction<Person> derived from PersonAction<object>. (Well, the constraint would necessarily remove PersonAction<object>.)

For
generic<T,U> where T:class where U:class
the result would have to be the Cartesian product of the inheritance hierarchy of both parameters.

I suspect a reason this isn't really desirable is that type safety would be lost on the base versions of itself. For example, if List<Fireman> could be used as List<Person>, this supposed list of Fireman could have Policeman objects added to it.

Interesting thought experiment.

At first glance the List<PersonAction<Person>> use case seems to be after the reverse, but it's really the same problem -- even in the interface or base class cases. If something operates on List<IAction> and calls, say, IAction.DoAction() on a PersonAction<Fireman> object with a PersonAction<Policeman> argument, the upcast will fail.

In order to do anything useful, you'd need to have special knowledge of desired behavior. Perhaps having a general implementation of DoAction() in a base class, and only calling it when the upcast fails. I don't know how you'd completely generalize such behavior in an implicit generic hierarchy.

I suppose the takeaway is that generics just doesn't bring anything new to the polymorphism story. I wonder if there are missed opportunities here.

I'm glad you posted this -- I hadn't ever thought about it before. And I happened to visit geekswithblogs at just the right time to see it :)
Gravatar
# re: Limitations of Generics Polymorphism
michelotti
12/6/2005 5:43 PM
Let me use another (slightly more real-world) example to illustrate. Let's take a typical Martin Fowler Mapper pattern where we have a PolicemanMapper and a
FiremanMapper each of which is responsible for saving/retrieving to the DB. We could define this:
abstract class PersonMapper
{
public abstract void SaveItem(Person person);
public abstract Person GetItem(int id);
}

class FiremanMapper : PersonMapper
{
public override void SaveItem(Person person)
{
Fireman fireman = person as Fireman;
if (fireman == null)
{
throw new ArgumentException("Invalid fireman object");
}
//save to DB here
}

public override Person GetItem(int id)
{
Fireman fireman = new Fireman();
//populate values from DB here
return fireman;
}
}

We must verify in the sub-class that the correct type is being sent in but it's a relatively small price to pay. Then we have a higher level controller that has to decide which mapper to invoke based on the object sent in:
class DataMapper
{
private Dictionary<Type, PersonMapper> mappers = new Dictionary<Type, PersonMapper>();

public DataMapper()
{
//Initialize cache of all mappers
mappers.Add(typeof(Policeman), new PolicemanMapper());
mappers.Add(typeof(Fireman), new FiremanMapper());
}

public void SaveItem(Person person)
{
//get appropriate mapper from the cache
PersonMapper mapper = mapper[person.GetType()];
//we have to correct person mapper, now make polymorphic call
mapper.SaveItem(person);
}
}

To avoid having to check the correct type inside each mapper sub-class, we could convert them all to be generic like this:
abstract class PersonMapper<T> where T : Person
{
public abstract void SaveItem(T item);
public abstract T GetItem(int id);
}

class FiremanMapper2 : PersonMapper<Fireman>
{
public override void SaveItem(Fireman item)
{
}

public override Fireman GetItem(int id)
{
}
}
However, if we do this now you can no longer invoke the mappers polymorphically out of the cache in the DataMapper class (without resorting to something like Interfaces). So I guess my point is - not only do generics not bring anything new to the polymorphic story - they are actually *more* limited.

I'm glad I'm not the only one interested in the topic though! :)
Gravatar
# Limitations of Generics Polymorphism
&lt;Rolog&gt;
12/6/2005 8:05 PM
Here is an interesting blog entry on the limitations of Generics polymorphism: http://www.geekswithblogs.net/michelotti/archive/2005/12/05/62273.aspx....
Gravatar
# re: Limitations of Generics Polymorphism
Random Reader
12/7/2005 1:50 AM
Yeah, I see what you mean. Incidentally, I stumbled across another blog entry about this: http://blogs.msdn.com/375079.aspx

Apparently the CLR actually has support for covariant/contravariant parameters on generic interfaces -- if a compiler chooses to make use of it. C# doesn't, unfortunately. I hadn't known about that.

Post Comment

Title *
Name *
Email
Comment *  
Verification

View Steve Michelotti's profile on LinkedIn

profile for Steve Michelotti at Stack Overflow, Q&A for professional and enthusiast programmers




Google My Blog

Tag Cloud