The Benefits of Using Generic Classes and Methods in C#

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:

  1. 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.
  2. 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.
  3. 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!

1. C# - INTRODUCTION

Image by rawpixel.com on Freepik


Enjoyed this post? Subscribe to my YouTube channel for more great content. Your support is much appreciated. Thank you!


Check out my Udemy profile for more great content and exclusive learning resources! Thank you for your support.
Ervis Trupja - Udemy



Enjoyed this blog post? Share it with your friends and help spread the word! Don't keep all this knowledge to yourself.