In C#, we can also have generic classes and generic methods i.e., classes and methods not made for a specific type but can be used with any general type.
We use <> brackets for this purpose. Suppose, we have defined a class or method with <T>
and performed each operation on T inside the method or the class. And we passed an integer while calling it - <int>
, then the T inside the class or the method will be changed to take int during the time of compilation.
Let's look at generic classes first.
C# Generic Class
Let's take an example.
using System;
class Generic<T>
{
private T genericVariable;
public Generic(T genericValue)
{
this.genericVariable = genericValue;
}
public void Display()
{
Console.WriteLine(this.genericVariable);
}
}
class Test
{
static void Main(string[] args)
{
Generic<int> g = new Generic<int>(5);
Generic<string> g1 = new Generic<string>("CodesDope");
g.Display();
g1.Display();
}
}
In the above example, we have defined a generic class - class Generic<T>
. Inside the class, we have treated T as normal data and declared a variable of type T - private T genericVariable;
.
In the constructor also, we are taking a variable of type T - public Generic(T genericValue)
. Remember that the type of T will be decided during making the object of the class.
Generic<int> g = new Generic<int>(5);
→ Here, the type of T is an integer. So, T will become int
inside the definition of the class.
Generic<string> g1 = new Generic<string>("CodesDope");
→ In the object g1, T is string. So, T will become a string inside the definition of the class.
After execution of Generic<int> g = new Generic<int>(5);
, Generic class would be something like:
class Generic
{
private int genericVariable;
public Generic(int genericValue)
{
this.genericVariable = genericValue;
}
public void Display()
{
Console.WriteLine(this.genericVariable);
}
}
Let's take one more example.
using System;
class Generic<T, U>
{
public T GenericVariableFirst
{
get;
set;
}
public U GenericVariableSecond
{
get;
set;
}
}
class Test
{
static void Main(string[] args)
{
Generic<int, string> g = new Generic<int, string>();
g.GenericVariableFirst = 10;
g.GenericVariableSecond = "abc";
Console.WriteLine(g.GenericVariableFirst);
Console.WriteLine(g.GenericVariableSecond);
}
}
In this example, we have defined our class to work on two generic types - T and U i.e., class Generic<T, U>
.
While making the object, we have set T as int
and U as string
- Generic<int, string> g
.
C# Constraint
We used a placeholder T in the above examples and this placeholder can be of any type. In C#, we can also constraint the type of placeholder using the where
keyword. Suppose, we have defined a class as:
class ClassName<T> where T: class
In this case, T can only be reference type like class, string, etc. If we try with something else, we will get an error.
Here, class
in where T: class
means T can be a reference type. Let's look at the table given below for another type of constraints.
Constraint | Description |
---|---|
class | Must be reference type |
struct | Must be value type |
new() | Must have public parameterless constructor. |
BaseClassName | Must be derivied from BaseClassName class. |
InterfaceName | Must implement InterfaceName interface. |
U | Must be or derive from the argument supplied for U. |
Let's take an example.
using System;
class Generic<T> where T: class
{
public T GenericVariable
{
get;
set;
}
}
class Test
{
static void Main(string[] args)
{
Generic<int> g = new Generic<int>();
g.GenericVariable = 10;
Console.WriteLine(g.GenericVariable);
}
}
In this example, we constrained the placeholder to take only reference type using class
and we tried to make an object with an integer for the placeholder. Since int
is a value type, we got errors during compiling the code.
Let's try with a reference type.
using System;
class Generic<T> where T: class
{
public T GenericVariable
{
get;
set;
}
}
class Test
{
static void Main(string[] args)
{
Generic<string> g = new Generic<string>();
g.GenericVariable = "CodesDope";
Console.WriteLine(g.GenericVariable);
}
}
In this example, we tried with a string which is a reference type and thus, the code compiled successfully.
C# Multiple Constraints
We can also have multiple constraints. Let's take an example.
using System;
class Generic<T, U> where T: class where U: struct
{
public T GenericVariableFirst
{
get;
set;
}
public U GenericVariableSecond
{
get;
set;
}
}
class Test
{
static void Main(string[] args)
{
Generic<string, int> g = new Generic<string, int>();
g.GenericVariableFirst = "CodesDope";
g.GenericVariableSecond = 10;
Console.WriteLine(g.GenericVariableFirst);
Console.WriteLine(g.GenericVariableSecond);
}
}
Inheritance With Generic Class in C#
We can derive a generic class to make subclasses of it. Let's take and example.
using System;
class Generic<T>
{
public T GenericVariable
{
get;
set;
}
}
class Derived: Generic<int>
{
}
class Test
{
static void Main(string[] args)
{
Derived d = new Derived();
}
}
In this example, we have made a subclass of a generic class and passed int
during deriving it - class Derived: Generic<int>
.
We can also make the derived class a generic class.
using System;
class Generic<T>
{
public T GenericVariable
{
get;
set;
}
}
class Derived<T>: Generic<T>
{
}
class Test
{
static void Main(string[] args)
{
Derived<int> d = new Derived<int>();
}
}
C# Generic Methods
We can also have generic methods similar to a generic class. Let's take an example.
using System;
class Test
{
static void Display<T>(T message)
{
Console.WriteLine(message);
}
static void Main(string[] args)
{
Display("CodesDope");
Display(10);
}
}
In this example, we have a generic method Display which has a placeholder T.
Display("CodesDope");
Display(10);
Firstly, we passed a string and then an integer to the function Display and it worked fine.