Published:
03 Jan 2007
|
Abstract
Generics allow us to create type safe collections with no boxing and un-boxing overhead. It is a concept that allows us to achieve parametric polymorphism. This article discusses this concept with examples. |
|
by Joydip Kanjilal
Feedback
|
Average Rating: This article has not yet been rated.
Views (Total / Last 10 Days):
37796/
62
|
|
|
Introduction |
C# is a strongly typed language, i.e., objects in C# should
have a type prior to holding any value. This enforces language interoperability
and security but restricts the programmer to specify the type of an object at
the time of writing the program as the type of an object in C# cannot be
determined at runtime. Generics are a new concept that has been introduced with
C# 2.0 and it helps us to defer the binding of a generic type to a data type
until its point of usage arrives. It is one of the most powerful and
anticipated feature of the C# 2.0 language. The basic idea behind generics is
to develop universal classes and methods that can accept a type as a parameter
to promote reusability, efficiency and maintainability of code. This article
discusses Generics, its usage, merits and demerits with code examples wherever
necessary.
|
What are Generics? |
According to MSDN, "Generics introduce to the .NET
Framework the concept of type parameters, which make it possible to design classes
and methods that defer the specification of one or more types until the class
or method is declared and instantiated by client code." By using Generics,
classes and methods can work uniformly on the values of different types. Generics
facilitate type safety, improved performance and reduced code. It promotes the
usage of parameterized types on our types and is also known as parametric
polymorphism. The Common Language Runtime (CLR) compiles any Generic type to IL
and Metadata as it does with the other types; but it stores added information
pertaining to the generic types which is used to bind the generic type to a specific
type at runtime when the generic type is instantiated. Note that for generic
types that are bound to value types, the generic types are instantiated for
each value type that it is bound to. Unlike this, for generic types that are
bond to reference types, the generic type instance refers to the location in
memory of the reference type to which it is bound for all the instances of the
generic type. The following code snippet in Listing 1 illustrates how a generic
type can be implemented.
Listing 1
public class Test<T>
{
public void Display()
{
//Some code
}
//Other members
}
We may say that the type parameter is an unbound type as it
is not bound to any specific type. When we instantiate this class, the generic
type has to be bound to a particular type as shown in the code snippet in
Listing 2.
Listing 2
Test<int> test = new Test<int>();
Note that in the code snippet above, the resulting type
called "test" is a bound type. The code example in Listing 3 shows
how we can use a generic method on varied types.
Listing 3: Implementing a generic method
using System;
using System.Collections.Generic;
class Test
{
static void Main( string[] args )
{
Test t = new Test();
int[] integerArray = {1,2,3,4,5,6};
char[] characterArray = { 'J', 'O', 'Y', 'D', 'I','P' };
double[] doubleArray = {0.1,0.2,0.3,0.4,0.5,0.6};
Console.WriteLine( "Displaying the contents of the integer array:--" );
t.Display(integerArray);
Console.WriteLine( "Displaying the contents of the character array:--" );
t.Display(characterArray);
Console.WriteLine( "Displaying the contents of the double array:--" );
t.Display(doubleArray);
}
public void Display< GenericArray >( GenericArray[] array )
{
for (int i = 0; i< array.Length; i++)
Console.WriteLine(array[i]);
}
}
The following points sum up the basic advantages of using
Generics.
·
Code Efficiency
·
Enhanced performance
·
Type Safety and reliability
·
Maintainability of code
|
How does C# Generics and C++ Templates compare? |
According to MSDN, "Generics and templates are both
language features that provide support for parameterized types. However, they
are different and have different uses." C# generics and templates in C++
are more or less similar syntactically. However, there are some notable differences
between them. C# Generic types are strong typed and they are instantiated at
the runtime whereas C++ Templates are loosely typed and they are instantiated at
the compile time only. Further, unlike C++ templates, Generics do not permit the
type parameters to have default values.
MSDN states, "C++ templates use a compile-time model.
When a template is used in a C++ program, the effect is as if a sophisticated
macro processor had been used. C# generics are not just a feature of the
compiler, but also a feature of the runtime. A generic type such as
List<T> maintains its generic-ness (genericity) after it has been
compiled. Or, to look at it another way, the substitution that the C++ compiler
does at compile time is done at JIT time in the C# generic world."
|
The System.Collections.Generic Namespace |
The System.Collections.Generic namespace contains several
generic collection classes based on generics and it is recommended that we
should use these collection classes in lieu of the earlier non-generic ones for
better performance of our applications.
According to MSDN, "The System.Collections.Generic
namespace contains interfaces and classes that define generic collections,
which allow users to create strongly typed collections that provide better type
safety and performance than non-generic strongly typed collections."
Note that the
System.Collections.Generic.ICollection<T> interface is the base interface
for all the classes in the System.Collections.Generic namespace.
|
Implementing a Generic Stack Class |
Let us understand the Generics with a simple example. Here I
have implemented a stack in two different ways. A stack that can be
recollected, works in the LIFO (Last-In-First-Out) principle. If we implement a
stack that can accept any data type or object and store the same in to it, we
have to opt for an object based stack (obviously if we are not using Generics).
I have given here two code examples of a partial implementation of a stack.
The first one is an object based stack and the next a
generic stack. The object based stack would require boxing and unboxing
operations to store and return objects in and out of the stack, but the generic
stack (the one that uses C# Generics) would not. Hence, as far as performance
is concerned, the generic stack is the better choice.
The code in Listing 4 is an example of an object based
stack. Note that we can store any type of data in this stack, but we need to
perform an explicit cast in order to retrieve the right type of the object that
was stored using boxing and unboxing. This can be a significant performance
overhead.
Listing 4
using System;
using System.Collections.Generic;
using System.Text;
namespace Generics
{
public class CustomStack
{
const int size = 10;
private object[] register;
private int count = 0;
private CustomStack()
{
register = new object[size];
}
public void Push(object x)
{
if (count < size)
register[count++] = x;
}
public object Pop()
{
return register[--count];
}
static void Main(string[] args)
{
CustomStack intStack = new CustomStack();
intStack.Push(10);
int i = (int)intStack.Pop();
Console.WriteLine(i);
Console.Read();
}
}
}
When using the object based stack shown in Listing 1, you
have to box and unbox the elements in order to push and pop them in and out of
the stack resulting in a significant performance drawback. Now, Generics comes
to the rescue. It is much more flexible and removes the overhead involved in
boxing and unboxing operations that we discussed earlier as a generic type is
assigned a specific type only at runtime. The type checks are done at the
compile time itself. Hence, the generic stack in our example works much faster
compared to its non-generic counterpart. The following is the implementation of
a generic stack using C# Generics.
Listing 5
using System;
using System.Collections.Generic;
using System.Text;
namespace Generics
{
public class CustomStack<S>
{
const int size = 10;
private S[] register;
private int count = 0;
public CustomStack()
{
register = new S[size];
}
public void Push(S x)
{
if (count < size)
register[count++] = x;
}
public S Pop()
{
return register[--count];
}
}
public class Test
{
static void Main(string[] args)
{
CustomStack<int> intStack = new CustomStack<int>();
intStack.Push(10);
int i = intStack.Pop();
Console.WriteLine(i);
Console.Read();
}
}
}
|
References |
|
Conclusion |
C# is a type safe language. This implies that an object
should have its type defined prior to an assignment of any value to it.
Generics, also known as parameterized types or parametric polymorphism, allow
us to have type parameters on our types. These generic types are assigned to a
specific type at the time of instantiation of the type at runtime. This deferred
instantiation of a generic type and its late binding to a specific type
promotes its usage on any type. This article has discussed Generics with code
illustrations wherever necessary. Happy reading!
|
|
|
User Comments
No comments posted yet.
|
|
|