Understanding Generics in C#: A Comprehensive Guide

Generics in C# provide a powerful way to create reusable, type-safe code. By using generics, you can write classes, methods, interfaces, and other structures that work with different data types while maintaining flexibility, type safety, and performance. This approach reduces the need for repetitive code and ensures your code is efficient and easier to manage.

c-generic

Published At Last Updated At
amitkumar
Amitkumar yadavDeveloperauthor linkedin
Table of Content
up_arrow

What Are Generics?

Generics let you create templates for classes, methods, or interfaces where the data type can be chosen later. This means you don’t have to write different versions of the same code for each type. Instead, you write one version that works for many types.

Example

public void PrintValue<T>(T value)

{

Console.WriteLine($"Value: {value}, Type: {typeof(T)}");

}

PrintValue(42);

PrintValue("Hello")

Output

Value: 42, Type: System.Int32 PrintValue("Hello");

Value: Hello, Type: System.String

  • Here, PrintValue is a generic method that accepts any type T.
  • Whether it's an int, string, or any other type, the method works seamlessly.


Types of Generics in C#

There are different ways to use generics in C#. Let’s look at them:

a. Generic Methods

Generic methods work with any type you pass to them. You set up a method with a placeholder type, and when you use the method, you fill in the actual type.

When to Use: When you need a method to work for different types of data.

Example

public void PrintValues<T1, T2>(T1 value1, T2 value2)

{

Console.WriteLine($"Value 1: {value1}, Type: {typeof(T1)}");

Console.WriteLine($"Value 2: {value2}, Type: {typeof(T2)}");

}

Usage

PrintValues(42, "Hello");

PrintValues(3.14, true);

Output

Value 1: 42, Type: System.Int32

Value 2: Hello, Type: System.String

Value 1: 3.14, Type: System.Double

Value 2: True, Type: System.Boolean

  • This method works with any two types (T1 and T2), making it very flexible.
  • Generic methods provide flexibility by allowing you to create a single method that can operate on multiple types.
  • This helps reduce code duplication and enhances reusability, making your code cleaner and more efficient.

b. Generic Classes

Generic classes can hold any type of data, which you decide when you create an instance of the class. This is useful when you want to store different kinds of data safely.

When to Use:

When you need a class that can store and work with different types.

Example

public class GenericList<T>

{

private List<T> items = new List<T>();

public void Add(T item) { items.Add(item); }

public T Get(int index) { return items[index]; }

}

Usage

var intList = new GenericList<int>();

intList.Add(1);

Console.WriteLine(intList.Get(0));

var stringList = new GenericList<string>();

stringList.Add("Hello");

Console.WriteLine(stringList.Get(0));

Output

1

Hello

  • Generic classes enable you to create versatile data structures that can hold different types while maintaining type safety.
  • This not only reduces the risk of errors but also enhances the reusability and maintainability of your code.

c. Generic Interfaces

Generic interfaces define a set of methods that can work with any type. Classes that use these interfaces can decide the type they want to use.

When to Use: When you want different classes to follow the same rules but work with different types of data.

Example

public interface IRepository<T>

{

void Add(T item);

T Get(int id);

}

public class InMemoryRepository<T> : IRepository<T>

{

private Dictionary<int, T> items = new Dictionary<int, T>();

private int currentId = 0;

public void Add(T item) { items[currentId++] = item; }

public T Get(int id) { return items[id]; }

}

Usage

IRepository<string> repo = new InMemoryRepository<string>();

repo.Add("Item 1");

Console.WriteLine(repo.Get(0));

Output

Item 1

  • This approach makes your code flexible because you can create repositories for any type of data while keeping the same structure and methods.
  • It helps different classes follow the same rules, even when they work with different kinds of information.

d. Generic Structs

Generic structs are like simple data groups that can hold different types. They are useful when you want to group related pieces of data.

When to Use: When you need to create a structure that works with different types of data.

Example

public struct Pair<T1, T2>

{

public T1 First { get; set; }

public T2 Second { get; set; }

public Pair(T1 first, T2 second)

{

First = first;

Second = second;

}

}

Usage

var pair = new Pair<int, string>(1, "One");

Console.WriteLine($"First: {pair.First}, Second: {pair.Second}");

Output

First: 1, Second: One

  • This allows you to create flexible and reusable structures that can hold different types of data while keeping them grouped together.

3. Generic Constraints

Sometimes, you want to limit the types that can be used with your generic classes or methods. This is where constraints come in. They ensure that the types meet specific conditions, like having a parameterless constructor or being a class.

When to Use: When you need to make sure the type provided has certain features.

Example

public T CreateInstance<T>() where T : new()

{

return new T();

}

public class MyClass

{

public string Name { get; set; } = "MyClass Instance";

} Usage

var instance = CreateInstance<MyClass>();

Console.WriteLine(instance.Name);

Output

MyClass Instance

  • This ensures you can only use types that fit the conditions you set, helping prevent errors and making your code more reliable.

4. Generics with Value Types

Generics can be used with both value types (like numbers) and reference types (like classes). This makes them very useful because you don’t need to worry about conversions between types.

Example

public class GenericCalculator<T> where T : struct

{

public T Add(T a, T b)

{

dynamic x = a;

dynamic y = b;

return x + y;

}

}

Usage

var calculator = new GenericCalculator<int>();

Console.WriteLine(calculator.Add(5, 10));

Output

15

  • This makes it easy to create reusable components that work with numbers or other value types, while keeping everything type-safe and efficient.

5. Using Generics in Collections:

List<T> and Dictionary<TKey, TValue>

C# uses generics a lot in collections like List<T> and Dictionary<TKey, TValue>. These make your code safer and faster.

Example with List<T>

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

foreach (var number in numbers)

{

Console.WriteLine(number);

}

Outputs

1 2 3 4

Example with Dictionary<TKey, TValue>

Dictionary<string, int> ageDictionary = new Dictionary<string, int>

{

{ "Alice", 30 },

{ "Bob", 25 }

};

Console.WriteLine(ageDictionary["Alice"]);

Output

30

  • Using these generic collections makes it easy to work with groups of data while ensuring that all items are of the correct type.
  • This results in fewer errors and better performance in your applications.

6. Static vs. Instance Methods in Generics

Generics can be used with both static and instance methods, but they behave differently:

1.Static Methods

  • These belong to the class itself, so you call them without creating an object.
  • They don’t access object-specific data because they work at the class level.

Use Case

Static methods are useful for utility functions or tasks that do not require data specific to an object.

Example

public static T GetDefaultValue<T>() where T : new()

{

return new T();

}

Usage

var defaultValue = GetDefaultValue<MyClass>();

Console.WriteLine(defaultValue.Name);

Output

MyClass Instance

  • In this example, the GetDefaultValue method creates a new instance of MyClass without needing an object.

2.Instance Methods

These need an instance of the class to work, meaning you need to create an object to use them.

They can access and change object data. Use Case

Instance methods are ideal for operations that deal with data unique to an instance, like adding or removing items from a list.

Example

public class Container<T>

{

private T item;

public void SetItem(T value) { item = value; }

public T GetItem() { return item; }

}

Usage

var container = new Container<int>();

container.SetItem(42);

Console.WriteLine(container.GetItem());

Output

42

  • In this example, the SetItem and GetItem methods work with the data stored in a specific instance of the Container class.

Summary

  • Static methods are called on the class itself and do not use instance data, making them great for general-purpose utilities.
  • Instance methods operate on specific object data and require an object to be instantiated, which is useful for handling state and individual data. Understanding when to use each type can help you design your classes more effectively.
  • Conclusion

    Generics in C# are a powerful tool that makes code flexible and safe. They let you create methods, classes, and structures that work with any type, making your programs efficient and easy to maintain. Whether you are dealing with collections, methods, or classes, understanding generics is key to writing good code in C#

    Schedule A call now

    Build your Offshore CreativeWeb Apps & Mobile Apps Team with CODE B

    We respect your privacy, and be assured that your data will not be shared