Exploring Generics in C# – A Comprehensive Guide with Examples

Exploring Generics in C Sharp

Exploring Generics in C#

Introduction

In the world of C#, generics play a crucial role in creating flexible and reusable code. They allow us to write functions and classes that can work with any data type, providing type safety and avoiding code duplication.
In this blog, we’ll dive deep into the fundamentals of generics, understand their benefits, and explore practical examples.

What are Generics

Generics in C# provide a way to define interfaces, classes, methods and collections with placeholder types. These placeholders are replaced with actual types when the code is compiled, making it highly flexible.

Advantages of Generics
    1. Code Re-usability: Write generic code that can be used with multiple data types.
    2. Type Safety: Catch type-related errors at compile-time rather than runtime.
    3. Performance: Avoid the need for boxing and unboxing, resulting in more efficient code.
    4. Readability: Enhance code clarity by writing generic algorithms and data structures.
Generics in Action with Examples

1. Generic Interfaces: Here is an example of a Generic Interface:

public interface IRepository<T> where T : class
{
Task<List<T>> Get();
Task<T> Get(int id);
void Create(T model);
}

Here ‘T’ is the placeholder and it can only be replaced by a class only as we have specified that in the where clause.
2. Generic Classes
: Below is the generic implementation of the above mentioned interface:

public class Repository<T> : IRepository<T> where T : class
{
   protected readonly AppDbContext _appDbContext;
   public Repository(AppDbContext appDbContext)
   {
      _appDbContext = appDbContext;
   }
   public Task<List<T>> Get()
   {
      return _appDbContext.Set<T>().ToListAsync();
   }
   public Task<T> Get(int id)
   {
      return _appDbContext.Set<T>().FindAsync(id).AsTask();
   }
   public void Create(T model)
   {
      _appDbContext.Set<T>().Add(model);
   }
}

and its usage as given below. Here placeholder ‘T’ has been replaced by ‘TodoGroup’ model class.

public interface ITodoGroupsRepository : IRepository<TodoGroup>
{
Task<TodoGroup> GetDuplicate(TodoGroup model);
}

public class TodoGroupsRepository : Repository<TodoGroup>, ITodoGroupsRepository
{
public TodoGroupsRepository(AppDbContext appDbContext) : base(appDbContext)
{
}
public Task<TodoGroup> GetDuplicate(TodoGroup model)
{
return _appDbContext.TodoGroups.FirstOrDefaultAsync(x => x.GroupName.ToLower() == model.GroupName.ToLower());
}
}

Here is another comparatively simpler generic class that represents a generic stack where we have not specified any type in the where clause. So ‘T’ can be replaced by any type in C#.

public class GenericStack<T>
{
private List<T> items = new List<T>();
public void Push(T item)
{
items.Add(item);
}
public T Pop()
{
if (items.Count == 0)
throw new InvalidOperationException("Stack is empty");
T poppedItem = items[items.Count - 1];
items.RemoveAt(items.Count - 1);
return poppedItem;
}
}

Usage of the ‘GenericStack’ class.
See how the placeholder ‘T’ is being replaced by ‘int’ and ‘string’ in the following examples.

GenericStack<int> intStack = new GenericStack<int>();
intStack.Push(1);
intStack.Push(2);
int poppedInt = intStack.Pop(); // Returns 2

GenericStack<string> stringStack = new GenericStack<string>();
stringStack.Push("Hello");
stringStack.Push("World");
string poppedString = stringStack.Pop(); // Returns "World"

3. Generic Methods: Now, let’s explore a generic method that compares two values:

public static bool AreEqual<T>(T value1, T value2)
{
return value1.Equals(value2);
}

Usage of the `AreEqual` method:

bool areEqualInt = AreEqual(5, 5); // Returns true
bool areEqualString = AreEqual("hello", "world"); // Returns false

4. Constraints in Generics: Sometimes, we may want to restrict the types that can be used with generics. Here’s an example using a constraint for the type to be a struct only in the where clause.

public class Calculator<T> where T : struct
{
public T Add(T a, T b)
{
return (dynamic)a + (dynamic)b;
}
}

Usage of the ‘Calculator’ class.
See how it allows only ‘int’ to replace placeholder ‘T’ as specified in the where clause because ‘int’ is a struct type in C# and ‘string’ is not.
It shows a compile time error immediately, implementing type-safety.

Calculator<int> intCalculator = new Calculator<int>();
int sum = intCalculator.Add(3, 5); // Returns 8

// The following will result in a compile-time error
// Calculator<string> stringCalculator = new Calculator<string>();

5. Generics in Collections: Many of the built-in collection classes in C# use generics.
For instance, the `List<T>` class:

List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };

This allows you to work with a list of integers, but the same `List` class can be used with any other type.

Conclusion

Generics in C# are a powerful feature that enhances code flexibility, re-usability, and type safety. By understanding and leveraging generics, you can write more robust and maintainable code. As you continue your C# journey, consider exploring advanced topics like covariance, contra variance, and the `where` keyword in generics to deepen your understanding. Keep Learning!

Exploring Generics in C# – A Comprehensive Guide with Examples
Scroll to top