Requiring Inherited Types in Generic Constraints

A generic class can specify that its generic parameter must inherit a type.  However, there is no obvious way in general to prevent clients from passing the base type itself.

For example, take the following set of types:

abstract class Entity  { }

class Person : Entity { }
class Boat : Entity { }
class Car : Entity { }

class Repository<TEntity> where TEntity : Entity { }

This allows the type Repository<Entity>, which doesn’t make logical sense.

In this particular case, we could prevent that by changing the generic constraint to where TEntity : Entity, new().  Since the base Entity class is abstract, that would disallow a Repository<Entity>.  However,if the concrete entities also  don’t have default constructors, this wouldn’t work.  Similarly, had the base type been an interface, we could add a : class constraint.

There is an (somewhat) ugly hack that can be used to prevent parameterizations of the base class in arbitrary cases (as long as you control every type involved):

abstract class Entity  { }
interface IConcreteEntity { }    //Marker interface

class Person : Entity, IConcreteEntity { }
class Boat : Entity, IConcreteEntity { }
class Car : Entity, IConcreteEntity { }

class Repository<TEntity> 
      where TEntity : Entity, IConcreteEntity { }

Specifically, we can add a marker interface which is implemented by all concrete implementations of the base type.  We can then constrain a generic parameter to inherit both the base type and the marker interface.  Since the base class itself does implement the marker interface, it will not be valid as a parameter. 
Note that the marker interface must be implemented (perhaps indirectly) by every single concrete implementation.

0 comments:

Post a Comment