locked
Generic Type Constraint Question RRS feed

  • Question

  • User1094877758 posted

    From reading this link: https://msdn.microsoft.com/en-us/library/bb384067.aspx, I understand that constraints can be put on the type parameter allowed for a class. So for example, I can write code like this:

    public class Draw<T>: where T : Shape

    This means that the type parameter that I can pass for the class can be only the Shape class. But isn't this the same as having the code below.

    public class Draw<Shape>


    If it's the same, then I don't see the benefit of having constraints on generic types.

    Thanks for the help!

    Monday, March 28, 2016 2:06 PM

Answers

  • User303363814 posted

    Coincidence!

    Today I was writing some code and realised that a generic base class constraint was what I wanted and could think of no other solution.

    The specifics don't matter too much but I was working with Entity Framework.  I have several classes that inherit from a base class called DbContext that is provided by Entity Framework.  My classes look like this, in part

    class ClubsContext : DbContext
    {
       static ClubsContext()
       {
          Database.SetInitializer<ClubsContext>(null);
       {
    
       public ClubsContext()
          : base("MyDbName")
       {
       }

    public DbSet<ClubState> Clubs {get; set;}
    public DbSet<SessionState> Sessions {get; set;}
    // etc }

    and

    class PlayersContext : DbContext
    {
       static PlayersContext()
       {
          Database.SetInitializer<PlayersContext>(null);
       {
    
       public PlayersContext()
          : base("MyDbName")
       {
       }
    
       public DbSet<PlayerState> Players {get; set;}
       public DbSet<BookingState> Bookings {get; set;}
       // etc
    }      

    The key thing to note is the similarity between the two classes and the duplication in the static and public constructors.  In particular note that each context calls the EF provided Database.SetInitializer which has a type parameter of the concrete Context that we are initializing.  Wanting to remove the code duplication I created a base generic class like this

    class BaseContext<TDbContext> : DbContext where TDbContext : DbContext
    {
       static BaseContext()
       {
          Database.SetInitializer<TDbContext>(null);
       }
    
       protected BaseContext()
          : base("MyDbName")
       { }
    }

    Note the use of the generic constraint which constrains TDbContext to be DbContext or one of its subtypes.  It is important to realise that this is not polymorphism, rather TDbContext is substituted with the particular value that the generic invocation provides.  My concrete contexts now look like this

    class ClubsContext : BaseContext<ClubsContext>
    {
       // declaration of DbSets as before
    }

    No constructors are need because the BaseContext does that ceremony in one place.  The generic base class ensures that SetInitializer is called with the correct xxxContext for each of my contexts.  The generic constraint ensures that I have access to the method.

    Hooray for base class generic constraints! My concrete classes are now simpler and there are fewer things to remember when I create the next Context as my application expands.

    I cannot think of a way to achieve this without the generic type parameter being constrained to the DbContext base class.

    Thanks Julie Lerman

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Friday, April 1, 2016 1:07 AM

All replies

  • User-434868552 posted

    @rds80

    (a) your first line of code does not compile (i'm using LINQPad 5):

    void Main()
    {
        
    }
    
    public class Shape { }
    
    public class Draw<T>: where T : Shape

    error messages:

    CS1031 Type expected
    
    CS1514 { expected
    
    CS1513 } expected

    fix this by dropping the colon and adding braces:

    public class Draw<T> where T : Shape { }

    your definition for your class Shape also requires braces.

    public class Shape{ }

    study carefully the link you provided https://msdn.microsoft.com/en-us/library/bb384067.aspx "where (generic type constraint) (C# Reference)"

    ditto this link:  https://msdn.microsoft.com/en-us/library/d5x73970.aspx "Constraints on Type Parameters (C# Programming Guide)" which is pointed to at the bottom of the where page above.

    (b) to answer your question:  a qualified   no 

    in OOP our  paradigm is to think as close as practical as if our end program is a model of real world objects.

    TIMTOWTDI

    your class Shape is more of a concept and as such could possibly be a base class 

    you can have methods that are members of Shape or you can other methods that use objects based on Shape

    N.B.:  instead of a class Draw it is more OOP-like to have a class DrawingObjects that has a generic member method Draw. 

    BOTTOM LINE:  if your poorly named generic class Draw could only draw a Shape then you could claim that your Draw definition and your Shape definition were somewhat equivalent (assuming your class Shape included a method to draw itself) ... however that would be a poorly crafted design.

    in the real world objects do things to themselves and have things done to them.

    person steers a car;  the engine of a car pulls fuel* from the gas tank; the radio in the car plays music that has been selected by the personet cetera, et cetera, et cetera

    * the person/car metaphor above is deliberately over-simplified ( https://en.wikipedia.org/wiki/Internal_combustion_engine )

    EDIT:

    DrawingObjects is not a very good class-name, better is DrawingVerbs or DrawingMethods or simply Drawing.   TIMTOWTDI

    END EDIT.

    Monday, March 28, 2016 6:23 PM
  • User36583972 posted

    Hi rds80,

    As far as I know, generics enable developers to maximize reuse of code and makes the code more simple, more secure.

    public class Draw<Shape>

    This is the general form of a generic class: class class-name <type-param-list> {}. In practice, we tend to have a certain type of T bound to limit the application area.

    So, have the constrained format below.

    where T: base-class-name

    public class Draw<T>: where T : Shape

    This is a base class constraint, we can specify a type argument must inherit the base class. Base class constraint form using the  following where clause:

    where T: base-class-name

    T is the name of the type parameter, the base-class-name is the name of the base class, there can specify only one base class.

    Best Regards,

    Yohann Lu

    Tuesday, March 29, 2016 8:04 AM
  • User303363814 posted

    I don't think there is much difference at all between  having a base class constraint on the generic type parameter and just specifying the type as in your second example.  There are only a few small things that I can think of.

    One is that if you are refactoring from, say, 'Shape' to 'Figure' then using the constraint method would reduce the number of places that the change needs to occur.  (Given the prevalence of refactoring tools this is probably of very little practical importance).  The reverse of this is that using the generic constraint would mean that the 'Draw' class would have the type parameter sprinkled though it whereas the second case would have the more descriptive 'Shape'.  This would be a good reason to use a more descriptive type parameter name, how about 'TShape' rather than the commonly used 'T'?  All things considered - pretty trivial.

    Any static data is unique for each closed type.  So this means that if you had a static field called Count then the constraint version would actually have a separate 'Count' for each subtype of 'Shape'.  That is, Draw<Triangle>.Count is separate from Draw<Square>.Count  With your second example there would be only one 'Count' static, regardless of the actual type used.  To get the same functionality in the non-constraint version you would probably need to maintain a dictionary indexed by concrete type, clumsy.

    I would also note that the generic type constraint is evaluated at runtime.  This may have some impact on the use of dynamic types when using your 'Draw' class.  I don't really know, just a thought bubble, you could make a simple test case if you really wanted to know.

    But ... don't throw out generic constraints just because one style of constraint does not seem to be essential for some cases.  Using generic constraints where the constraint is one or more interfaces along with maybe the 'new()' constraint is quite useful and I cannot think of an alternate way to implement.

    Wednesday, March 30, 2016 12:29 AM
  • User303363814 posted

    Coincidence!

    Today I was writing some code and realised that a generic base class constraint was what I wanted and could think of no other solution.

    The specifics don't matter too much but I was working with Entity Framework.  I have several classes that inherit from a base class called DbContext that is provided by Entity Framework.  My classes look like this, in part

    class ClubsContext : DbContext
    {
       static ClubsContext()
       {
          Database.SetInitializer<ClubsContext>(null);
       {
    
       public ClubsContext()
          : base("MyDbName")
       {
       }

    public DbSet<ClubState> Clubs {get; set;}
    public DbSet<SessionState> Sessions {get; set;}
    // etc }

    and

    class PlayersContext : DbContext
    {
       static PlayersContext()
       {
          Database.SetInitializer<PlayersContext>(null);
       {
    
       public PlayersContext()
          : base("MyDbName")
       {
       }
    
       public DbSet<PlayerState> Players {get; set;}
       public DbSet<BookingState> Bookings {get; set;}
       // etc
    }      

    The key thing to note is the similarity between the two classes and the duplication in the static and public constructors.  In particular note that each context calls the EF provided Database.SetInitializer which has a type parameter of the concrete Context that we are initializing.  Wanting to remove the code duplication I created a base generic class like this

    class BaseContext<TDbContext> : DbContext where TDbContext : DbContext
    {
       static BaseContext()
       {
          Database.SetInitializer<TDbContext>(null);
       }
    
       protected BaseContext()
          : base("MyDbName")
       { }
    }

    Note the use of the generic constraint which constrains TDbContext to be DbContext or one of its subtypes.  It is important to realise that this is not polymorphism, rather TDbContext is substituted with the particular value that the generic invocation provides.  My concrete contexts now look like this

    class ClubsContext : BaseContext<ClubsContext>
    {
       // declaration of DbSets as before
    }

    No constructors are need because the BaseContext does that ceremony in one place.  The generic base class ensures that SetInitializer is called with the correct xxxContext for each of my contexts.  The generic constraint ensures that I have access to the method.

    Hooray for base class generic constraints! My concrete classes are now simpler and there are fewer things to remember when I create the next Context as my application expands.

    I cannot think of a way to achieve this without the generic type parameter being constrained to the DbContext base class.

    Thanks Julie Lerman

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Friday, April 1, 2016 1:07 AM