Generics, Constrains, Covariance, And Contra Variance

Generics is a powerful feature that allows us to write type safe and reusable code. Generic class enables us to write code without worrying about actual type and defers the type specification to the consumer class or client. This allows us to keep the internal algorithms same for different types. In essence, Generic allows us to parameterize the type used inside a class.

  1.  //Generic Class  
  2.   
  3.  public class Repository<T>  : IRepository<T>  where T:class  
  4.     {  
  5.         protected readonly DbContext _context;  
  6.         public IEnumerable<T> GetALL() { return _context.Set<T>().ToList<T>(); }  
  7.   
  8.     }  
  9. //Generic Interface  
  10.   
  11.  public interface IRepository<T> where T : class  
  12.     {  
  13.         IEnumerable<T> GetALL();  
  14.     }  
  15. //Generic Method  
  16.   
  17.  public static Nullable<T> GetQueryString<T>(string key) where T :  IConvertible  
  18.     {  
  19.        T result = default(T);  
  20.   
  21.        if  (String.IsNullOrEmpty(HttpContext.Request.Query[key]) == false)  
  22.             {  
  23.                 string value = HttpContext.Request.Query[key].ToString();  
  24.   
  25.                 try  
  26.                 {  
  27.                     result = (T)Convert.ChangeType(value, typeof(T));  
  28.                 }  
  29.                 catch  
  30.                 {  
  31.                     //Could not convert.  Pass back default value...  
  32.                     result = default(T);  
  33.                 }  
  34.             }  
  35.   
  36.             return result;  
  37.         }  
  38. // Generic Delegate  
  39.      public delegate void notify<T>(T target) where T : class;   

Generic Constraints

Constraints help us to define the allowed types and also restricts the consumer from improper use of the class.

T should be a value type : class Repository<T> where T:struct

T should be a reference type : class Repository<T> where T:class

T should have a parameter less constructor: class Repository<T> where T:new()

T should inherit from a type : class Repository<T> where T:Entity

T should implement a interface : class Repository<T> where T:IEntity

Multiple Constrains

When we specify multiple constarints, there are some orders that need to be followed. For example, base class constraint should come first and the new() constraint should come last etc.

class Requester<T> where T:Customer,IDiscouts

class Requester<T> where T:Customer,IDiscouts,new()

Different Generics type declaration

unbound type 

An unbound Generic type cannot exist as a instantiated object, but only as a system.type reference. In addition to that, it will not have any type arguments specified.

typeof(List <>)

Constructed type

An Constructed Generic type has at least one type argument specified.

class sample <T,T>

class sample <T,string>

class sample <int,string>

Open Generic type

Any type which has a type parameter.

class sample <T,T>

class sample <T,string>

Closed type

class sample <int,string>

Covariance in Generics

This allows a generic class to reuse the logic available in the base class. In addition to that, covariance can be applied to the methods which returns type T.

ContraCovariance in Generics

This allows a generic class to use the logic available in the sub class. In addition to that, covariance can be applied to the methods which return type T.

This is available in .NET 4.0 and enables us to follow two SOLID Principles called Liskove substitution and Interface Segregation Principle.

The following example shows the parent class and sub classes.

  1. public interface IEntity  
  2. {  
  3.         bool IsValid();  
  4.         }   
  5.     public class User   
  6.         {  
  7.         public int UserId { get; set; }  
  8.         }  
  9.   
  10.   public class Customer : User , IEntity  
  11.     {  
  12.         public int customerId { get; set; }  
  13.         public bool IsValid()  
  14.         {  
  15.             return true;  
  16.         }  
  17.     }  
  18.   
  19.   public class RewardCustomer : Customer:   
  20.     {  
  21.         public int RewardId { get; set; }  
  22.     }   

RePositories

  1. public interface IUserRepository<out T>  
  2.     {  
  3.         T FindById(int id)  
  4.     }  
  5.   
  6.     public interface IRewardRepository<in T>  
  7.     {  
  8.          void AddRewardUser(T entity);  
  9.     }  
  10.   
  11.     public interface ICustomerRepository<T> : IUserRepository<T>,IRewardRepository<T>  
  12.     {  
  13.           
  14.     }  

Concrete Repository

  1. public class CustomerRepository <T> : ICustomerRepository<T> where T: class, IEntity  
  2.     {  
  3.         DbContext _ctx;  
  4.         DbSet<T> _set;  
  5.         public void AddRewardUser(T entity)  
  6.         {  
  7.             if (entity.IsValid())  
  8.             {  
  9.                 _set.Add(entity);  
  10.             }  
  11.         }  
  12.         public T FindById(int id)  
  13.         {  
  14.             return _set.Find(id);  
  15.         }  
  16.     }  

Consumer Class Customer Controller

  1. private static void AddRewardUser(IRewardRepository<RewardCustomer> customerRepository)  
  2.         {  
  3.             customerRepository.Add(new RewardCustomer { RewardId = 100 });  
  4.             customerRepository.Commit();  
  5.         }  

Consumer Class User Details Page

  1. private  void GetUser(IUserRepository<User> customerRepository)  
  2.         {  
  3.             var user = customerRepository.FindById();  
  4.              
  5.         }  

Consumer Class Customer Details Page

  1. private void QueryCustomers(ICustomerRepository<Customer> customerRepository)  
  2.         {  
  3.             var customer = customerRepository.FindById(1);  
  4.              
  5.         }