Introduction to Generics in C#
Generics in C# provide a powerful tool for writing reusable and type-safe code. They allow you to define classes, interfaces, and methods with placeholders for the types they operate on. By using generics, you can create components that work with any data type, promoting code flexibility and efficiency. This approach enhances code readability and maintainability by reducing the need for repetitive implementations tailored to specific types. Generics enable developers to write algorithms and data structures that are not tied to a particular data type, resulting in more versatile and scalable software solutions. In essence, generics facilitate the creation of highly adaptable and robust codebases in C#.
Table of Content
- Introduction
- What is Generics Class in C# ?
- Syntax of Generics in C#
- How Generics Work
- Example#1
- Example#2
- Example#3
- Advantages
What is Generics Class in C#?
A generic class in C# is a template that allows you to define a class with placeholders for one or more data types. These placeholders, known as type parameters, are specified when creating an instance of the class, enabling the class to work with any data type. This flexibility promotes code reusability and type safety. Generic classes are commonly used to create collections, such as lists and dictionaries, that can store elements of any type while maintaining compile-time type checking. By leveraging generic classes, developers can write more flexible and efficient code that adapts to various data types without sacrificing readability or performance.
Syntax of Generics in C#
Let us look at the syntax of a generic declaration in C#. It is a very simple syntax. As a common practice, the letter ‘T’, in the capital and enclosed within angular brackets, denotes the declaration of a generic code in C#. But, this is not the mandate. You can use any letter in capital case enclosed within angular brackets to denote a Generic code.
Declaring a Generic Class:
public class MyGenericClass<U>
Instantiating a Generic Class:
MyGenericClass<int> = new MyGenericClass<int>();
Declaring a Generic Derived Class:
public class MyGenericDerivedClass<T> : MyGenericBaseClass<T>
Declaring a Generic Method:
public T MyGenericMethod(T item);
How Generics Work in C#?
When you declare a generic code in C#, the compiler produces a template equivalent to that code. This template is checked for all compilation errors except type-safety. The next step comes when the generic code is invoked or called in another part of the program. At the time of invocation, you specify the type that your generic code would be compiled with. When the compiler reaches the invocation point, it inserts the type specified in the previously compiled template. This is then re-compiled to check for type-safety. Once passed, the code is ready for execution. We would see the compiled code in the examples below to get a better understanding of generic templates.
Example of Generics in C#
Example#1
Below are the different examples of Generics:
Generics with Class
Code:
using System;
using System.Collections.Generic;
public class GenericClass<T>
{
List<T> genericList;
public GenericClass()
{
genericList = new List<T>();
}
public void AddToList(T item)
{
genericList.Add(item);
}
public void DisplayList()
{
foreach ( var ele in genericList )
{
Console.Write("{0}\t", ele);
}
}
}
public class Program
{
public static void Main()
{
GenericClass<int> intGenericObj = new GenericClass<int>();
GenericClass<string> stringGenericObj = new GenericClass<string>();
intGenericObj.AddToList(28);
intGenericObj.AddToList(999);
intGenericObj.AddToList(0);
intGenericObj.AddToList(-123);
intGenericObj.AddToList(100);
stringGenericObj.AddToList("Hello");
stringGenericObj.AddToList("Bonjour");
stringGenericObj.AddToList("Ola");
stringGenericObj.AddToList("Ciao");
stringGenericObj.AddToList("Hallo");
intGenericObj.DisplayList();
Console.WriteLine("\n");
stringGenericObj.DisplayList();
}}
Output:
The same code can also be re-written as below. This illustrates the power of defining a generic class that can be made type-safe for multiple types in a single object.
using System;
using System.Collections.Generic;
public class GenericClass<T, U>
{
List<T> genericList1;
List<U> genericList2;
public GenericClass()
{
genericList1 = new List<T>();
genericList2 = new List<U>();
}
public void AddToList(T item1, U item2)
{
genericList1.Add(item1);
genericList2.Add(item2);
}
public void DisplayList()
{
foreach (var ele in this.genericList1)
{
Console.Write("{0}\t", ele);
}
Console.WriteLine("\n");
foreach (var ele in this.genericList2)
{
Console.Write("{0}\t", ele);
}
}
}
public class Program
{
public static void Main()
{
GenericClass<int, string> genericObj = new GenericClass<int, string>();
genericObj.AddToList(28, "Hello");
genericObj.AddToList(999, "Bonjour");
genericObj.AddToList(0, "Ola");
genericObj.AddToList(-123, "Ciao");
genericObj.AddToList(100, "Hallo");
genericObj.DisplayList();
}
}
Output:
Compiled Code:
To get a perspective of how the data-type is resolved in Generics, let us look at the compiled code generated when we instantiate the class with integer and string types in the above example.
using System;
using System.Collections.Generic;
public class GenericClass
{
List<int> genericList1;
List<string> genericList2;
public GenericClass()
{
genericList1 = new List<int>();
genericList2 = new List<string>();
}
public void AddToList(int item1, string item2)
{
genericList1.Add(item1);
genericList2.Add(item2);
}
public void DisplayList()
{
foreach (var ele in this.genericList1)
{
Console.Write("{0}\t", ele);
}
Console.WriteLine("\n");
foreach (var ele in this.genericList2)
{
Console.Write("{0}\t", ele);
}
}
}
public class Program
{
public static void Main()
{
GenericClass genericObj = new GenericClass();
genericObj.AddToList(28, "Hello");
genericObj.AddToList(999, "Bonjour");
genericObj.AddToList(0, "Ola");
genericObj.AddToList(-123, "Ciao");
genericObj.AddToList(100, "Hallo");
genericObj.DisplayList();
}
}
Example#2 : Generics with Method
Code:
using System;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
int[] intArr = {12, 23, 43, 94, 35};
double[] doubleArr = {12.3, 45.6, 98.7, 1.45, 82.653};
string[] strArr = {"Hello", "Bonjour", "Ola", "Ciao", "Hallo"};
Console.WriteLine("The largest integer in the array is {0}", findMax(intArr));
Console.WriteLine("The largest floating-point number in the array is {0}", findMax(doubleArr));
Console.WriteLine("The largest string in the array is {0}", findMax(strArr));
}
static T findMax<T>(T[] items)
where T : IComparable<T>
{
T max = items[0];
int position = 0;
for (int i = 1; i < items.Length; i++)
{
if (items[i].CompareTo(max) > 0)
{
max = items[i];
position = i;
}
}
return max;
}
}
Output:
Example#3
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
// Creating a list of integers
List<int> intList = new List<int>();
// Adding elements to the list
intList.Add(10);
intList.Add(20);
intList.Add(30);
// Iterating through the list
Console.WriteLine("Elements in the integer list:");
foreach (int num in intList)
{
Console.WriteLine(num);
}
// Creating a list of strings
List<string> stringList = new List<string>();
// Adding elements to the list
stringList.Add("Apple");
stringList.Add("Banana");
stringList.Add("Orange");
// Iterating through the list
Console.WriteLine("\nElements in the string list:");
foreach (string fruit in stringList)
{
Console.WriteLine(fruit);
}
}
}
Output:
Explanation
- The List<T> class is instantiated with specific data types (int and string), enabling developers to create lists tailored to their needs while maintaining type safety.
- By using generics, there’s no need for casting when working with List<T>, as the compiler ensures type compatibility.
- The generic nature of List<T> allows it to work seamlessly with a wide range of data types, making it highly versatile in real-world C# applications.
- Generics facilitate code reusability and readability, enhancing the efficiency of managing collections by providing type-safe data structures.
- List<T> represents a fundamental tool in C# programming for storing and manipulating collections of various data types efficiently and reliably.
The System.Collections.Generic namespace
The System.Collections.Generic namespace in C# contains interfaces and classes defining the generic collections. They allow the programmers to create generic collections that are better in performance and as strongly-types as the non-generic collections. This namespace contains Lists, Dictionaries, Linked Lists, Hashes, Key-Value Pairs, Stacks, etc, all of which are generic in nature. The programmers can implement them in their code.
Importance of C# Generics
Below is the Importance of C# Generics as follows:
- Generics Allow Code-Reusability: the fundamental principle of good programming. You need not write the same code for each expected data-type. You simply define a type-independent code and tell the compiler that the actual data-type would be provided at the time of code invocation.
- Prevent the Cost of Boxing and Un-Boxing: Of course, the use of generics can be bypassed through object class. The below two pieces of code are equivalent in their tasks.
Generic Code: public T MyFunc(T item);
Non-Generic Code: public object MyFunc(object item)
The object class supersedes all classes and thus the above non-generic code can also be used to generate type-independent code templates. But, there is a huge performance gap between the two codes. Using the object class incurs an additional cost of boxing and unboxing of the data-types. Generics eliminate this and are thus better in performance.
Advantages of Generics in C#
- Code Reusability: Generic classes allow you to write code that can work with any data type, promoting reusability across different scenarios without the need for redundant implementations tailored to specific types.
- Type Safety: Generic classes provide compile-time type checking, ensuring that the code is type-safe. This helps catch type-related errors early in the development process, reducing the likelihood of runtime exceptions.
- Performance: By using generics, you can avoid the overhead of boxing and unboxing operations associated with non-generic collections, resulting in better performance and reduced memory consumption.
- Readability: Generic classes enhance code readability by eliminating the need for casting or converting objects to specific types. This makes the code more intuitive and easier to understand.
- Flexibility: Generic classes offer flexibility by allowing developers to create components that can work with a wide range of data types. This flexibility makes the code more adaptable to changing requirements and promotes a more agile development process.
- Maintainability: Since generic classes promote code reuse and readability, they contribute to better maintainability by reducing the complexity of the codebase and minimizing the risk of introducing bugs when making changes or additions.
Conclusion
Generics in C# empower developers to craft reusable, type-safe code by defining classes, interfaces, and methods with type placeholders. This flexibility enhances code efficiency, readability, and maintainability by eliminating the need for repetitive implementations specific to certain types. By accommodating any data type, generics facilitate the creation of versatile algorithms and data structures, fostering scalable software solutions. Syntax-wise, generics are denoted by enclosing a type parameter within angular brackets, typically represented by the letter ‘T’. Leveraging generics ensures code reusability, type safety, and improved performance while promoting flexibility and maintainability in C# development.
Recommended Articles
We hope that this EDUCBA information on “C# Generics” was beneficial to you. You can view EDUCBA’s recommended articles for more information.