Syntax of Generics in C#
The basic syntax for defining generics in C# involves specifying the type parameter inside angle brackets (< >) after the class, method, or interface name.
1. Generic Class Syntax:
public class MyClass<T>
{
private T data;
public MyClass(T value)
{
data = value;
}
public T GetData()
{
return data;
}
}
In this example:
- T is the placeholder for the type that will be specified later.
- You can create an instance of MyClass using any type: MyClass<int>, MyClass<string>, etc.
2. Generic Method Syntax:
public T Add<T>(T a, T b)
{
return a + b;
}
In this example:
- The method Add is generic, and the type T will be inferred based on the parameters passed when calling the method.
3. Generic Interface Syntax:
public interface IMyInterface<T>
{
void DoSomething(T item);
}
Generics in C# with example
Let us look at an example to see why Generics are important in C#. We write a simple program to determine whether two integer numbers have the same value. The code for this is shown below. In this case, we named two classes CheckEqualOrNot and MainClass. The areBothEqual() method in the CheckEqualOrNot class takes two integer values as input parameters and checks whether the two input values are equal or not. If both the values are equal, it returns true; otherwise, it returns false. And we're calling the static areBothEqual() method from the MainClass class and displaying the output based on the return value.
C#
using System;
namespace GenericsDemo
{
public class MainClass
{
static void Main()
{
// creating an instance of CheckEqualOrNot class
CheckEqualOrNot obj= new CheckEqualOrNot();
// isEqual stores the true or false value
bool isEqual= obj.areBothEqual(10,10);
if(isEqual==true){
Console.WriteLine("Both the values are equal");
}
else{
Console.WriteLine("Both the values are not equal");
}
}
}
public class CheckEqualOrNot
{
// simple function that returns true if both the values
// are same, otherwise it returns false
public bool areBothEqual(int value1, int value2)
{
if(value1 == value2){
return true;
}
else{
return false;
}
}
}
}
Output:
Both the values are equal
The issue with the above program is that we have to create multiple functions to compare values of different data types. For example, to check if two values of string data type are the same or not, we need one more function.
Example:
C#
using System;
namespace GenericsDemo
{
public class MainClass
{
static void Main()
{
// creating an instance of CheckEqualOrNot class
CheckEqualOrNot obj= new CheckEqualOrNot();
// checks for integer values
bool isEqual= obj.areBothEqual(10,10);
if(isEqual==true){
Console.WriteLine("Both the values are equal");
}
else{
Console.WriteLine("Both the values are not equal");
}
// creating an instance of CheckEqualOrNot class
CheckEqualOrNot obj1= new CheckEqualOrNot();
//checks for two string values
bool isEqual1= obj1.areBothEqual("Ram","Lakshman");
if(isEqual1==true){
Console.WriteLine("Both the values are equal");
}
else{
Console.WriteLine("Both the values are not equal");
}
}
}
public class CheckEqualOrNot
{
// it receives two integer parameters
public bool areBothEqual(int value1, int value2)
{
if(value1 == value2){
return true;
}
else{
return false;
}
}
// receives two string parameters
public bool areBothEqual(string value1, string value2)
{
if(value1 == value2){
return true;
}
else{
return false;
}
}
}
}
Output:
Both the values are equal
Both the values are not equal
Solutions To The Above Problem
We saw that to compare variables of different data types, we had to create different functions which could take the values of the data type which we wanted to compare. If we try to pass integer values for a string parameter, there will be an error.
There are two ways to solve the problem:
- Make use of the object data type as the parameters.
- Use generics.
Object Data Type
One of the ways to make the above areBothEqual() method to accept string type values as well as integer type values is to make use of the object data type as the parameters. If we make the parameters of the areBothEqual() method as Object type, then it is going to work with any data type.
Example:
C#
using System;
namespace GenericsDemo
{
public class MainClass
{
static void Main()
{
// creating an instance of CheckEqualOrNot class
CheckEqualOrNot obj= new CheckEqualOrNot();
// checks for integer objects
bool isEqual= obj.areBothEqual(12,10);
checkAndPrint(isEqual);
CheckEqualOrNot obj1= new CheckEqualOrNot();
// checking for string objects
bool isEqual1= obj1.areBothEqual("Ram","Ram");
checkAndPrint(isEqual1);
}
// function to check the value and print the result
static void checkAndPrint(bool result){
if( result == true){
Console.WriteLine("Both the values are equal");
}else{
Console.WriteLine("Both the values are not equal");
}
}
}
public class CheckEqualOrNot
{
// the data types provided are converted to objects
// we can provide any data type
public bool areBothEqual(object value1, object value2)
{
if(value1 == value2){
return true;
}
else{
return false;
}
}
}
}
Output:
Both the values are not equal
Both the values are equal
Since the output of the above code is as expected, you may feel that this is the best solution for our problem. However, there are certain flaws in creating object data types.
- The overall performance of the program is reduced and compromised due to boxing and unboxing. Boxing is a mechanism to explicitly convert a value type to a reference type, whereas unboxing is the process of converting back the reference type into the value type. Whenever we pass an integer or a string or a float value in the areBothEqual() method, it is converted to object type. We are actually wasting time in boxing and unboxing.
- Since the parameters are object types, we can pass two values of different data types, and there will be no error. However, it makes no sense to compare two different data types. The program now no longer remains strongly typed.
Because of these two big flaws with the object data types, the concept of generics was brought up.
Generics
The solution to the above problems is Generics in C#. With generics, we will make the areBothEqual() method to work with different types of data types. The most common way of defining a Generic class is by putting the <T> sign after the class name. It is not mandatory to put the "T" word in the Generic type definition. We can also use any other word.
Generic Method
In order to make the areBothEqual() method generic (generic means the same method will work with the different data type), we specified the type parameter T using the angular brackets <T>.
Example:
C#
using System;
namespace GenericsDemo
{
public class MainClass
{
static void Main()
{
// creating an instance of CheckEqualOrNot class
CheckEqualOrNot obj= new CheckEqualOrNot();
// whenever we call the areBothEqual<T>() method
//we specify the data type for which the comparison is made.
//if two different data type is provided there will be compile-time error
// specify the parameter using <string>
bool isEqual1= obj.areBothEqual<string>("Ram","Ram");
checkAndPrint(isEqual1);
// specify the parameter using <int>
bool isEqual2= obj.areBothEqual<int>(10,10);
checkAndPrint(isEqual2);
// specify the parameter using <int>
bool isEqual3= obj.areBothEqual<int>(10,12);
checkAndPrint(isEqual3);
// specify the parameter using <float>
bool isEqual4= obj.areBothEqual<float>(2.34f,2.35f);
checkAndPrint(isEqual4);
// specify the parameter using <double>
bool isEqual5= obj.areBothEqual<double>(6.8765,6.8765);
checkAndPrint(isEqual5);
}
// function to check the value and print the result
static void checkAndPrint(bool result){
if( result == true){
Console.WriteLine("Both the values are equal");
}else{
Console.WriteLine("Both the values are not equal");
}
}
}
public class CheckEqualOrNot
{
//the generic method
// created by just using angular<T>
//We do not specify a particular data type for the parameters
// even they are generic
// Ex: we don't write string value1, rather we write T value1
// The data type for value1 is specified by the user while calling the function
public bool areBothEqual<T>(T value1, T value2)
{
if(value1.Equals(value2)){
return true;
}
else{
return false;
}
}
}
}
Output:
Both the values are equal
Both the values are equal
Both the values are not equal
Both the values are not equal
Both the values are equal
Explanation:
- In order to make the areBothEqual() method generic, we specified the type parameter T using the angular brackets <T>.
- In order to call the areBothEqual() method, we must specify the data type on which the method should operate while calling the function.
- This is how a function is made generic.
Creating a Generic Class
Just as we made a method generic, we can make the entire class generic.
Example:
C#
using System;
namespace DemonstrateGenerics
{
// creating a generic class
// simply add <T> after the class name
public class GenericClass<T>
{
public T genericVariable; // variable of generic type
// generic method
// simply add <T> after method name and specify the parameters as generic
public void genericMethod<T>(T value1, T value2)
{
Console.WriteLine("{0}", genericVariable);
Console.WriteLine("Value1:{0}", value1);
Console.WriteLine("Value2: {0}", value2);
}
}
// This class calls the instances of the generic class
class NormalClass
{
static void Main(string[] args)
{
/*
The below code shows how to Instantiate a Generic class.
For every instance, its respective data type must be specified.
If we try to provide a value that is not of the same type
as specified, there will be a compile-time error.
*/
// Instantiate Generic Class, string is the type argument
GenericClass<string> stringObj = new GenericClass<string>();
stringObj.genericVariable = "Want to become a coder?";
stringObj.genericMethod("Coding", "Ninja");
Console.WriteLine("********************");
// Instantiate Generic Class, int is the type argument
GenericClass<int> intObj = new GenericClass<int>();
intObj.genericVariable = 100;
intObj.genericMethod(200, 300);
Console.WriteLine("********************");
// Instantiate Generic Class, double is the type argument
GenericClass<double> doubleObj = new GenericClass<double>();
doubleObj.genericVariable = 50.7;
doubleObj.genericMethod(109, 2345.789);
}
}
}
Output:
Want to become a coder?
Value1:Coding
Value2: Ninja
********************
100
Value1:200
Value2: 300
********************
50.7
Value1:109
Value2: 2345.789
Features of Generics
The features of generics are:
Type Safety
One of the biggest features of generics is that they are type-safe. If you try to use a data type other than the one specified in the definition, you will get a compile-time error.
Code Reusability
Generics increase the reusability of the code. By making the methods and variables generic, we don't have to write code again and again to handle different data types.
Enhanced Performance
If we use object data type, then there is boxing and unboxing involved in the process. However, using generics we can avoid boxing and unboxing and hence save crucial time which is used for boxing and unboxing. This increases overall performance.
Advantages of C# Generics
- Type Safety: Generics ensure that type errors are caught at compile time, preventing runtime errors related to type mismatches. This eliminates the need for casting and minimizes the risk of runtime exceptions.
- Code Reusability: With generics, you can create flexible classes, methods, and data structures that work with any data type, leading to reusable code across different scenarios without rewriting.
- Performance: Generics help avoid boxing and unboxing operations, which are common when using non-generic collections with value types. This improves performance, particularly in scenarios involving large amounts of data.
- Increased Maintainability: By using generics, the codebase becomes easier to maintain since you avoid duplication. The same generic class or method can handle different types, making it more concise and easier to update.
- Cleaner Code: Generic types make the code more concise and readable by eliminating the need for explicit casting or writing multiple overloaded methods or classes for each type.
- Better Compatibility with LINQ: C#'s LINQ (Language Integrated Query) works seamlessly with generics, allowing you to write efficient, type-safe queries over collections without the need for casts or conversions.
- Supports Strongly Typed Collections: Generic collections such as List<T>, Dictionary<TKey, TValue>, and Queue<T> provide strongly-typed data structures that ensure the correct type is always used, reducing runtime errors.
- Flexibility in Data Handling: Generics allow you to create algorithms and data structures that are independent of the data type, enabling you to handle any type of data without losing type safety.
Frequently Asked Questions
When should I use generics in C#?
Use generics in C# when you need reusable, type-safe code that works with multiple data types without sacrificing performance or flexibility.
What is generics in C# Unity?
In Unity, generics in C# allow for creating reusable scripts and data structures that can work with any type of data, improving code flexibility and maintainability.
Why are generics used?
It is possible to create a single class, interface, or method that automatically works with different types of data by using generics.
How can we make a method generic?
To make a method generic, we specify the type parameter T using the angular brackets <T>.
How do generics improve the performance of the program?
Generics we can avoid boxing and unboxing and hence save crucial time. This increases overall performance.
Conclusion
In this article, we have extensively discussed Generics in C# along with its code implementation in C#.
- Generics simply refers to parameterized types. Parameterized types are useful because they allow us to create classes, interfaces, and methods that take as a parameter the type of data on which they operate.
- Generics provided the type safety that was previously lacking.
- The most common way of defining a Generic class is by putting the <T> sign after the class name. It is not mandatory to put the "T" word in the Generic type definition.