Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A Simple Introduction to Generics

0.00/5 (No votes)
24 Apr 2007 1  
This article uses a simple example to illustrate the importance of Generics in C# 2.0

Introduction

This article uses a simple list scenario to illustrate the importance of Generics.

Background

C# 2.0 is a powerful language with some interesting features, including partial classes, nullable types, anonymous methods, generics, and so many others.

Of all these features, I find Generics interesting, especially when dealing with lists or a collection of Items.

In this article, I use a simple list scenario to show the importance of Generics.

Simple Definition

Generics are code templates that allow developers to create type-safe code without referring to specific data types.

In simple terms, Generics are classes, structures, interfaces and methods that have placeholders for one or more of the types they store or use.

Simple List Scenario

Consider a scenario where you have two lists of values i.e. list1 and list2, where list1 contains integer values and list2 contains strings as shown below:

List1 = {1,2,3,4} 
List2 = {"string1","string2","string3"} 

Now assume you want to develop a class which can be used to store either of the list items (list1 items or list2 items).

Since both integers and strings can be treated as "objects" in C#, you decide that your class will store an array of objects, so you come up with the class in code below.

Class Used as a Collection of Objects

// Use this class to store a collection of objects
    public class MyObjCollection
    {
        object[] items = new object[50]; // limit items
        int numitems = 0;
        public void Add(object item)
        {
            if (numitems + 1 < 50)
            {
                items[numitems] = item;
                numitems++;
            }
        }

       // This enables to retrieve an item by using mygencollection[2];
        public object this[int n]
        {
           get { return items[n]; }
        }
    }

Now to store the list1 items (integers) using this class, we use the code below:

MyObjCollection objintcollection = new MyObjCollection(); 
objintcollection.Add(1); 
objintcollection.Add(2); 
objintcollection.Add(3); 
objintcollection.Add(4); 

Note: Since the Add(object item) method expects an object, we can also add other datatypes to this collection other than integers. For example:

objintcollection.Add("string 1");

Now if we wanted to store list2 items, we would have similar code, only that we name our instance differently:

MyObjCollection objstringcollection = new MyObjCollection(); 
objstringcollection.Add("string1"); 
objstringcollection.Add("string2");
objstringcollection.Add("string3");

Still the system would allow us to add non string values to this collection, e.g.

objstringcollection.Add(3); 

Type-Safety

The fact that we can add non integer values to the collection storing list1 values and we can also add non string values to the collection storing the list2 values, shows that our collections are not type-safe.

Type Casting

To retrieve a value from any of these collections, type casting has to be done, e.g.

Int value1 = (int)objintcollection[0];

Or:

string str1 = (string)objstringcollection[0]; 

This is a problem, because type casting reduces performance.

InvalidCastExceptions

The C# compiler will always compile even if you are casting an integer value to a string or vice versa, however you will get an exception at run time. For example, the code below would compile:

string value1 = (string)objintcollection[0];

However remember that you stored integers in the collection, so at runtime you will get an InvalidCastException. To realise the importance of Generics while dealing with lists, let's consider a Generic class, which stores an array of Generic types as opposed to an array of objects, in code below.

Generic Class stores a collection of values of a given datatype.

public class MyGenCollection<T> 
{ 
  T[] items = new T[50]; 
  int numitems = 0; 
  public void Add(T item) 
  {
    if (numitems + 1 < 50) 
    {
      items[numitems] = item; 
      numitems++; 
    }
  }
// This enables to retrieve an item by using mygencollection[2]; 
  public T this[int n] 
  { 
    get { return items[n]; } 
  }
}

Note: This class is similar to the object collection class, but instead of having a collection of objects, we have a collection of the Generic type T. In the declaration of the class, i.e. public class MyGenCollection<T>, <T> is a place holder, and T is a type argument to be replaced by any datatype. Assuming you want to use the above class to store the list1 items (integers), we would use the code below:

MyGenCollection<int> intcollection = new MyGenCollection<int>(); 
intcollection.Add(1); 
intcollection.Add(2); 
intcollection.Add(3); 
intcollection.Add(4);

Note: The Add(int item) method in this case only accepts values of type int, so adding any other datatype will cause a compile time error. For example, the code below will not compile:

intcollection.Add("string 1"); 

To store the list2 items(strings), we would instantiate the class as below:

MyGenCollection<string> stringcollection = new MyGenCollection<string>(); 
stringcollection.Add("string1"); 
stringcollection.Add("string2"); 
stringcollection.Add("string3"); 

This makes the Generic collection class type safe, considering you specify to the system the datatype of the values you want to store in the collection.

Type Casting Not Needed

Also note that you don't need to type cast any values retrieved from the collection. For example, the code below is enough to retrieve the first item of the integer collection:

int value1 = intcollection[0]; 

No InvalidCastExceptions

Since there are no datatype casts being done, the Invalid Cast Exceptions are eliminated.

Generic Methods

Methods can also be defined with Generic type parameters, for example:

public void doAction<T>(T argument) 
{} 

You can also have:

public T doAction<T>(T argument) 
{}

T is the type argument and can be specified when the method is being called, for example:

Myobject.doAction<int>(56); 

Note: Since type T is generic, some datatype specific operations may not be possible within the method body, for example the code below wouldn't compile:

public T GetSum<T>(T arg1, T arg2) 
{ 
  return arg1+arg2; 
} 

However with Inheritance, a Generic abstract method can be declared and a datatype passed at implementation of the method, for example:

public abstract class MyClassA<T> 
{ 
  public abstract T GetDifference (T arg1, T arg2); 
} 

public class MyClassB: MyClassA<int> //integer type specified 
{ 
  public int GetDifference(int arg1, int arg2)
  { 
    return arg1 - arg2; 
  } 
}

Conclusion

Generics are handy especially when you wish to develop code templates that can be used with any datatype at any given moment. Generics are also applicable in other concepts of programming including inheritance, for example Generic Interfaces, Reflection, Remoting, Delegates, etc. It is important that developers master the working of Generics, to develop advanced solutions that are manageable, scalable and with good performance.

Phillip Walera
Sys Analyst/Developer
Founder Sharepoint Africa

History

  • 24th April, 2007: Initial post

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here