In C#, the term “generic” refers to a type that is not limited to a specific data type. A generic type is declared by including a type parameter, represented by <T>
, after the type name, such as in TypeName<T>
.
C# offers the capability to define generic classes, methods, events, delegates, and operators using the type parameter <T>
without specifying a particular data type. The type parameter acts as a placeholder for any data type that may be specified later. This blog post will cover the creation and usage of generic classes and methods.
Generic classes and methods are important in C# because they provide several benefits, including:
- Reusability – By using generic classes and methods, you can write code that can work with multiple data types without the need to create a separate version for each data type. This makes your code more reusable and reduces the amount of code you need to write.
- Performance – Generic classes and methods allow you to write efficient code that avoids the overhead of type conversions. This is because the type of the data is known at compile-time, allowing the compiler to generate optimized code.
- Code maintainability – By using generic classes and methods, you can write code that is easier to maintain and understand. This is because you can define a single implementation that works with multiple data types, making it easier to change and test.
Generic Method
Generics are really useful if you want to create reusable methods. Below, is an example of a generic method in C# that returns the maximum value between two inputs of any comparable type:
public static T Max<T>(T a, T b) where T : IComparable<T> { return (a.CompareTo(b) > 0) ? a : b; }
This method uses the where T : IComparable<T>
constraint to ensure that the type passed in as the generic parameter implements the IComparable<T>
interface, which is an interface that provides a method for comparing objects of a specific type.
Integer example:
int x = 15, int y = 25; int maxValue = Max(x, y); // returns 25
Using the same method you can also compare strings.
String example:
string firstName = "Ervis"; string lastName = "Trupja"; string maxString = Max(firstName , lastName ); // returns "Trupja"
Generic Class
An example of a generic class would be a class that can be used with different data types.
Example:
public class GenericExampleClass<T> { private T value; public GenericExampleClass(T value) { this.value = value; } public T GetValue() { return value; } }
In the example above, the GenericExampleClass
class is defined with a type parameter T
, which can be replaced with any type (e.g., int
, string
, double
, etc.) when the class is instantiated. The value
field is of type T
, and the GetValue
method returns value of type T
.
String example
GenericExampleClass<string> stringClass = new GenericExampleClass<string>("Welcome to dotNET; How?"); string value = stringClass.GetValue(); Console.WriteLine(value); // outputs: "Welcome to dotNET; How?"
In this example, we first create an instance of the GenericExampleClass
class with the type parameter set to string
, and assign it to the stringClass
variable. We then use the GetValue
method to retrieve the value stored in the stringClass
instance and write it to the console.
Integer example:
GenericExampleClass<int> intClass = new GenericExampleClass<int>(123); int value = intClass.GetValue(); Console.WriteLine(value); // outputs: 123
In this example, we first create an instance of the GenericExampleClass
class with the type parameter set to int
, and assign it to the intClass
variable. We then use the GetValue
method to retrieve the value stored in the intClass
instance and write it to the console.
Repository Pattern Using Generic Classes
In C#, generic repository patterns use generic classes to provide a strongly-typed, reusable implementation of a repository. This allows the same repository code to work with multiple types of entities, without having to write separate repository implementations for each type.
First, you need to create a generic interface. In our case, it will include only two methods, but you can add other methods to delete data, update data, etc.
public interface IEntityBaseRepository<T> where T: class { Task<IEnumerable<T>> GetAllAsync(); Task AddAsync(T entity); }
Next, you need to create a generic implementation of the interface, which would look like the below:
public class EntityBaseRepository<T> : IEntityBaseRepository<T> where T : class { private readonly AppDbContext _context; public EntityBaseRepository(AppDbContext context) { _context = context; } public async Task<IEnumerable<T>> GetAllAsync() { return await _context.Set<T>().ToListAsync(); } public async Task AddAsync(T entity) { await _context.Set<T>().AddAsync(entity); await _context.SaveChangesAsync(); } }
This code defines a generic repository class, EntityBaseRepository
, for entities that implement the IEntityBaseRepository
interface.
The class injects an AppDbContext
object in its constructor, which is used to interact with the database. The repository class has two methods: GetAllAsync
and AddAsync
.
The GetAllAsync
method returns all the entities of a particular type as a list, and the AddAsync
method adds a new entity to the database and saves the changes.
But, how can you use the generic repository above?
If you have, for example, an ActorService
, to use this base repository, all you need to do is inherit from it and pass the AppDbContext
as a parameter. Then, you will have access to both methods automatically.
Example:
public class ActorsService : EntityBaseRepository<Actor> { public ActorsService(AppDbContext context) : base(context) { } }
The code above would be equivalent to the code below:
public class ActorsService : EntityBaseRepository<Actor>, IActorsService { public ActorsService(AppDbContext context) : base(context) { } public async Task<IEnumerable<Actor>> GetAllAsync() { return await _context.Set<Actor>().ToListAsync(); } public async Task AddAsync(Actor entity) { await _context.Set<Actor>().AddAsync(entity); await _context.SaveChangesAsync(); } }
If you have another service named SchoolService
, you can inherit from EntityBaseRepository
public class SchoolsService : EntityBaseRepository<School> { public SchoolsService(AppDbContext context) : base(context) { } }
In SchoolsService, School is the entity class and in ActorsService the Actor is the entity class.
You can check another example in my GitHub Repository.
Check out the tutorials below to learn more!
Image by rawpixel.com on Freepik