Generics in C# are a way to create code that can be used with different data types. This makes the code more versatile and reusable. Generics are declared using the <> symbol. For example, the following code declares a generic method called Print:
public static void Print<T>(T value)
{
Console.WriteLine(value);
}
This method can be used to print any data. For example, the following code prints an integer and a string:
Print(10);
Print("Hello, world!");
Generics can also be used to create generic classes. For example, the following code declares a generic class called List:
public class List<T>
{
private T[] items;
public List()
{
items = new T[0];
}
public void Add(T item)
{
items = Array.Resize(items, items.Length + 1);
items[items.Length - 1] = item;
}
public T Get(int index)
{
return items[index];
}
}
This class can be used to store any data type. For example, the following code creates a list of integers and adds some numbers to it:
List<int> numbers = new List<int>();
numbers.Add(10);
numbers.Add(20);
numbers.Add(30);
Generics are a powerful tool that can be used to make code more versatile and reusable. They are a fundamental part of the C# language and are used in many different scenarios.
Here are some of the benefits of using generics in C#:
- They make code more versatile. Generic code can be used with different data types, which makes it more reusable.
- They make code more concise. Generic code can often be written more concisely than non-generic code.
- They can improve performance. Generic code can sometimes improve performance by eliminating the need for type casts.
- They can help to prevent errors. Generic code can help prevent errors by ensuring the correct data types are used.
If you are new to C#, I recommend learning about generics early on. They are a powerful tool that can make your code more versatile, concise, and efficient.
Generic Classes
The Generic class can be defined by putting the <T> sign after the class name. Putting the "T" word in the Generic type definition isn't mandatory. You can use any word in the TestClass<> class declaration.
public class TestClass<T> { }
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 types with length 5. The Add() method is responsible for adding any objects into the collection, and the Indexer property implements foreach statement iteration. Finally, in the main class, we instantiated the TestClass<T> class with an Integer type reference and added some integer type elements into the collection using the Add() method.
using System;
using System.Collections.Generic;
namespace GenericApp
{
public class TestClass<T>
{
// define an Array of Generic type with length 5
T[] obj = new T[5];
int count = 0;
// adding items mechanism into generic type
public void Add(T item)
{
//checking length
if (count + 1 < 6)
{
obj[count] = item;
}
count++;
}
//indexer for foreach statement iteration
public T this[int index]
{
get { return obj[index]; }
set { obj[index] = value; }
}
}
class Program
{
static void Main(string[] args)
{
//instantiate generic with Integer
TestClass<int> intObj = new TestClass<int>();
//adding integer values into collection
intObj.Add(1);
intObj.Add(2);
intObj.Add(3); //No boxing
intObj.Add(4);
intObj.Add(5);
//displaying values
for (int i = 0; i < 5; i++)
{
Console.WriteLine(intObj[i]); //No unboxing
}
Console.ReadKey();
}
}
}
After building and running this program, the output of the program is as shown in the following;
Figure 1.2 - Simple Generic Example
Some significant characteristics of Generic types 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, which can sometimes result in a great disaster. The following example shows adding an integer, string, and object to the collection of an ArrayList type;
ArrayList obj = new ArrayList();
obj.Add(50);
obj.Add("Dog");
obj.Add(new TestClass());
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;
foreach(int i in obj)
{
Console.WriteLine(i);
}
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;
Test<int> obj = new Test<int>();
obj.Add(50);
obj.Add("Dog"); //compiler error
obj.Add(new TestClass()); //compiler error
Performance
Another feature of Generics is performance. Using value types with non-generic collection classes results in boxing and unboxing overhead when a value type is converted to a 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 occurs.
ArrayList obj = new ArrayList();
obj.Add(50); //boxing- convert value type to reference type
int x= (int)obj[0]; //unboxing
foreach(int i in obj)
{
Console.WriteLine(i); // unboxing
}
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 occur as in the following;
TestClass<int> obj = new TestClass<int>();
obj.Add(50); //No boxing
int x= obj[0]; // No unboxing
foreach(int i in obj)
{
Console.WriteLine(i); //No unboxing
}
Binary Code reuse
Generics provide a kind of source code protection. A Generic class can be defined once and instantiated with many different types. Generics can be defined in one CLR-supported language and used in another .NET language. The following TestClass<T> is instantiated with an int and string types:
TestClass<int> obj = new TestClass<int>();
obj.Add(50);
TestClass<string> obj1 = new TestClass<string>();
Obj1.Add("hello"); 
Join the conversation! Your thoughts help the community grow.