Generics in C# and .NET procedure many of the benefits of strongly-typed collections as well as provide a higher quality of and a performance boost for code. Generics are very similar to C++ templates but having a slight difference in such a way that the source code of C++ templates is required when a templates is instantiated with a specific type and .NET Generics are not limited to classes only. In fact they can also be implemented with Interfaces, Delegates and Methods. The detailed specification for each collection is found under the System.Collection.Generic namespace.
Boxing and Unboxing
.Net defines two major categories of data type termed value type and reference type to represent a variable. This is where boxing and unboxing are needed. Boxing is a mechanism to explicitly convert a value type to a reference type by storing the variable into System.Object; when you box the value the CLR allocates a new object into the heap and copies the value type's value into that instance. For example, you have created a variable of int type as:
The opposite operation is Unboxing which is the process of converting back the reference type into the value type. This process verifies that the receiving data type is equivalent to the boxed type as;
The C# compiler sees the assignment from int to object and vice-versa. When this program is compiled and you examine the IL generated code via IL dissembler, you notice that the program respond by inserting a box instruction in the IL automatically when b is assigned the value of a and an unbox instruction when c is assigned the value b as in the following;
Figure 1.1 - IL opcode
The code loads the constant 20 and stores it in the local slot; it the loads the value 20 onto the stack and boxes it. Finally it loads the boxed 20 back onto the stack and unboxes it into an int.
There are series of operations performed by .NET CLR, such as, first an object is allocated in the managed heap, then in boxing the value is transformed into the memory location and during unboxing the value is stored on the heap and must be transferred back to the stack. So the Boxing and Unboxing process has a significant importance in Generics from the performance point of view because this process is more resource-intensive rather than using Generics.
Generic Classes
The Generic class can be defined by putting the <T> sign after the class name. It isn't mandatory to put the "T" word in the Generic type definition. You can use any word in the TestClass<> class declaration.
The System.Collection.Generic namespace also defines a number of classes that implement many of these key interfaces. The following table describes the core class types of this namespace.
Generic class | Description |
Collection<T> | The basis for a generic collection Comparer compares two generic objects for equality |
Dictionary<TKey, TValue> | A generic collection of name/value pairs |
List<T> | A dynamically resizable list of Items |
Queue<T> | A generic implementation of a first-in, first-out (FIFO) list |
Stack<T> | A generic implementation of a last-in, first-out (LIFO) list |
Simple Generic Class Example
The following example shows a simple Generic type manipulation. The TestClass<T> defines an array of generic type with length 5. The Add() method is responsible for adding any type of objects into the collection and the Indexer property is an implementation of foreach statement iteration. Finally in the main class we instantiated the TestClass<T> class with an Integer type reference and adds some integer type elements into the collection using the Add() method.
After building and running this program, the output of the program is as shown in the following;
Figure 1.2 - Simple Generic Example
There are some significant characteristics of Generic types that make them special to the conventional non-generics type as follows;
Type Safety
Performance
Binary Code reuse
Type Safety
One of the most significant features of Generics is Type Safety. In the case of the non-generic ArrayList class, if objects are used, any type can be added to the collections that can sometimes result in a great disaster. The following example shows adding an integer, string and object to the collection of an ArrayList type;
Now, if the collection is iterated through the foreach statement using integer elements, the compiler accepts the code but because all the elements in the collection are not an integer, a runtime exception occurs;
The rule of thumb in programming is that Errors should be detected as early as possible. With the generic class Test<T>, the generic type T defines what types are allowed. With the definition of Test<int>, only an integer type can be added to the collection. The compiler doesn't compile the code because the Add() method has invalid arguments as follows;
Performance
Another feature of Generics is performance. Using value types with non-generic collection classes result in boxing and unboxing overhead when a value type is converted to reference type and vice-versa.
In the following example, the ArrayList class stores objects and the Add() method is defined to store some integer type argument. So an integer type is boxed. When the value from ArrayList is read using the foreach statement, unboxing occurrs.
Note
Generics are faster than other collections such as ArrayList.
Instead of using objects, a Generics type of the TestClass<T> class is defined as an int, so an int type is used inside the class that is generated dynamically from the compiler. Therefore boxing and unboxing no longer occurs as in the following;
Binary Code reuse
Generics provide a kind of source code protection. A Generic class can be defined once and can be instantiated with many different types. Generics can be defined in one CLR supported language and used from another .NET language. The following TestClass<T> is instantiated with an int and string types:
Generic Methods
While most developers will typically use the existing generic types within the base class libraries, it is certainly possible to build your own generic members and custom generic types.
The objective of this example is to build a swap method that can operate on any possible data type (value-based or reference-based) using a single type parameter. Due to the nature of swapping algorithms, the incoming parameters will be sent by reference via ref keyword.
After compiling this Generic method implementation program, the output is as in the following;
Figure 1.3 - Generic Methods
Dictionary
Dictionaries are also known as maps or hash tables. It represents a data structure that allows you to access an element based on a key. One of the significant features of a dictionary is faster lookup; you can add or remove items without the performance overhead.
.Net offers several dictionary classes, for instance Dictionary<TKey, TValue>. The type parameters TKey and TValue represent the types of the keys and the values it can store, respectively.
Simple Example of Dictionary
The following example demonstrates simple dictionary collections using Generics. In this program, a Dictionary type object is created that accepts an int as the key and a string as the value. Then we add some string values into the dictionary collection and finally displaying the dictionary collection elements.
The following example portrays some more complexities by defining an addition class emp where we are overriding the ToString() method to display the name and salary of a particular employees. Later in the Main() method , a new Dictionary<TKey,TValue) instance is created, where the key is of type string and the value is of type emp. The constructor allocates a capacity of 2 elements. The emp objects and string value as a key is added to the dictionary collection. Finally the collection elements are iterated using a foreach statement and displayed on the screen.
Queues
Queues are a special type of container that ensures the items are being accessed in a FIFO (first in, first out) manner. Queue collections are most appropriate for implementing messaging components. We can define a Queue collection object using the following syntax:
The Queue collection property, methods and other specification definitions are found under the Sysyem.Collection namespace. The following table defines the key members;
System.Collection.Queue Members | Definition |
Enqueue() | Add an object to the end of the queue. |
Dequeue() | Removes an object from the beginning of the queue. |
Peek() | Return the object at the beginning of the queue without removing it. |
The following demonstrates a basic Queues type collection, adds some string type values into the collection and finally displays the entire items into the collection using the while statement.
Stacks
A Stack collection is an abstraction of LIFO (last in, first out). We can define a Stack collection object using the following syntax:
The following table illustrates the key members of a stack;
System.Collection.Stack Members | Definition |
Contains() | Returns true if a specific element is found in the collection. |
Clear() | Removes all the elements of the collection. |
Peek() | Previews the most recent element on the stack. |
Push() | It pushes elements onto the stack. |
Pop() | Return and remove the top elements of the stack. |
The following demonstrate a stack collection. First an array type object is referenced into the stack collection. Then the value of the elements in the collection are removed from the stack using the Pop() method and displayed on the screen.
In another example with a Generic implementation, 5 items are added to the stack with the Push() method. With the foreach statement, all the items are iterated using the IEnumerable interface. The enumerator of the stack does not remove the items; it just returns each item in a LIFO manner as in the following:
No comments:
Post a Comment