Monday, June 7, 2010

Generics Overview

 

In this article we will discuss –

1.       Why we need generics?

2.       How to implement Generics?

3.       How compiler helps us?

4.       Other features

 

Why we need generics?              

To get the relevance of generics we have to walk through C# 1.1 world.

Performance issue in 1.1

Consider the case where we need to have a class that deals with any type. The class will be like –

  1: public class GenericClass
  2:     {
  3:         public object a { get; set; }
  4:         public object Invoke()
  5:         {
  6:             return a;
  7:         }
  8:     }
  9: 
 

If I want to deal with integer type on this class –

  1: GenericClass myGenericClass = new GenericClass();
  2: myGenericClass.a = 1;
  3: int b = (int)myGenericClass.Invoke();

                In this case the integer value 1 is assigned to object property. Integer is a value type and hence to hold this integer value, a new object is created in memory and the integer value is taken into it. This is called boxing.

                When you are retrieving the integer value back to ‘b’, the integer value stored in the object is retrieved. This is unboxing.

                So obviously boxing and unboxing takes more memory and more time, When we deal with large collections, this memory and time consumption is signinificant that it reduces the overall performance.

Consider the case of dealing with reference types -

  1: GenericClass myGenericClass = new GenericClass();
  2: myGenericClass.a = "1" ;
  3: string b = (string)myGenericClass.Invoke();
  4: 

                 A string object is assigned to the property ‘a’ and the same is retrieved from ‘Invoke()’ to ‘b’. This actually involves Casting from one type to another (String to Object and Object to String). This also affects performance.

Solution in 1.1

                We can resolve this performance issue by making our class strongly typed. Ie, for using integer types, define class as –

  1: public class IntClass
  2:     {
  3:         public int a { get; set; }
  4:         public int Invoke()
  5:         {
  6:             return a;
  7:         }
  8:     }
  9: 

For string-

  1: public class StringClass
  2:     {
  3:         public string a { get; set; }
  4:         public string Invoke()
  5:         {
  6:             return a;
  7:         }
  8:     }
  9: 

                When we use such strongly typed classes, boxing, unboxing and casting will be avoided and hence the performance will be enhanced.

Drawback for 1.1 solution

                We can go for separate class declarations for each type (string, int, etc). But if you are using this class declaration for ‘n’ types, you have to declare ‘n’ different classes (differ only in type used inside the method).

Solution in 2.0

                From above examples you can see that different classes we declare is following same template and the type is replaced with the type we need.

                In 2.0 generics concept is introduced to avoid the headache of declaring different classes for types we need.

 

In generics the things that we discussed above is split among developer and compiler -

How to implement Generics?

                For above mentioned example, generic class is -

  1: public class GenericClass<T>
  2:     {
  3:         public T a { get; set; }
  4:         public T Invoke()
  5:         {
  6:             return a;
  7:         }
  8:     }
  9: 
 10: 

                It is nothing but you declared a class that can be used as template for class declaration using any type.

When you define a variable for generic class –

  1: GenericClass<int> intClass = new GenericClass<int>();

The type you need is passed instead of ‘T’ in declaration.

So developer simply declares the template and request for a class declared for the type he needed.

How compiler helps us?

When developer defines variable as –

  1: GenericClass<int> intClass = new GenericClass<int>();

                Compiler access the template defined and replaces all ‘T’s with type requested by developer and eventually declares a new class as –

  1: public class IntGenericClass
  2:     {
  3:         public int a { get; set; }
  4:         public int Invoke()
  5:         {
  6:             return a;
  7:         }
  8:     }
  9: 
 10: 

 

                Then the variable ‘intClass’ points to the instance created for new class declared ‘IntGenericClass’.

                By this way the strongly typed classes are defined by compiler for any type you need.

Other features

                You can restrict the types that can be used to declare classes from your generic template.

Type constraint

  1: public class GenericClass<T> where T : IList,ICollection 
  2:     {
  3:         public T a { get; set; }
  4:         public T Invoke()
  5:         {
  6:             return a;
  7:         }
  8:     }
  9: 
 10: 

                This is called type constraint where you are saying ‘T’ should implement interface ‘IList’ and ‘ICollection’.

                So in this case you can define –

  1: GenericClass<ArrayList> arrayClass = new GenericClass<ArrayList>();

 

Because ArrayList is implementing IList and ICollection.

But you cannot do following definition –

  1: GenericClass<int> intClass = new GenericClass<int>();

  1: public class GenericClass<T> where T : class 
  2:     {
  3:         public T a { get; set; }
  4:         public T Invoke()
  5:         {
  6:             return a;
  7:         }
  8:     }
  9: 
 10: 

                This means you can use only refernce types as ‘T’.

Right-

  1: GenericClass<ArrayList> arrayClass = new GenericClass<ArrayList>();

Wrong –

  1: GenericClass<int> intClass = new GenericClass<int>();

Constructor Constraint

  1: public class GenericClass<T> where T : new()
  2:     {
  3:         public T a { get; set; }
  4:         public T Invoke()
  5:         {
  6:             return a;
  7:         }
  8:     }
  9: 
 10: 

              

 This says that ‘T’ should have the default constructor

                Right –

  1:                                 GenericClass<ArrayList> arrayClass = new GenericClass<ArrayList>();

                Wrong –

  1: GenericClass<string> stringClass = new GenericClass<string>();

You can make use of Type constraint and constructor constraint at the same time -

  1: public class GenericClass<T> where T :IList, new()
  2:     {
  3:         public T a { get; set; }
  4:         public T Invoke()
  5:         {
  6:             return a;
  7:         }
  8: }
  9: 
 10: 

 

1 comment:

  1. Nice Article.Easy to understand the concepts. Presentation style is superb.

    ReplyDelete