Generics in C# let you write classes, methods, interfaces, and data structures that work with different data types while keeping type safety. Instead of writing separate versions of the same logic for integers, strings, doubles, and custom objects, you can write the logic once and let the type be supplied later.
This is one of the most important features in modern C# because it improves code reuse, readability, and safety. Many of the collections and framework types you use every day, such as List<T>, Dictionary<TKey, TValue>, Task<TResult>, and Nullable<T>, are built on generics.
Without generics, developers would often fall back to object and repeated type casting. That approach is less safe, less expressive, and can also add runtime overhead. Generics solve that by moving type flexibility into the design itself.
What Are Generics in C#?
Generics are a feature that allow type parameters to be used in place of actual data types. Those type parameters are filled with real types when the class or method is used.
public class Box<T>
{
public T Value { get; set; }
}
Here, T is a type parameter. It is a placeholder for a real type such as int, string, or a custom class.
Why Generics Are Important
Generics solve several problems at once. They reduce code duplication, improve compile-time checking, remove unnecessary casting, and make APIs easier to understand because the expected data types are clear in the method or class signature.
If a method is meant to work on many types, generics express that intent directly. Instead of weakening the design with object, generics preserve correctness while still allowing flexibility.
Generic Type Parameter Syntax
A generic type parameter is written inside angle brackets. By convention, single-letter names such as T, TKey, TValue, and TResult are common.
public class Storage<T>
{
public T Data { get; set; }
}
When using the class, the actual type is supplied by the caller.
Storage<int> numberStorage = new Storage<int>();
Storage<string> textStorage = new Storage<string>();
Generic Class in C#
A generic class is a class defined with one or more type parameters. It can hold values, expose methods, and model reusable logic across many data types.
public class Pair<T>
{
public T First { get; set; }
public T Second { get; set; }
}
This class can represent a pair of integers, a pair of strings, or a pair of any other type without rewriting the class each time.
Generic Method in C#
You do not need a whole generic class to use generics. A single method can also be generic.
public static T GetFirst<T>(T first, T second)
{
return first;
}
This method works for any type as long as both arguments use the same type parameter.
int a = GetFirst(10, 20);
string b = GetFirst("A", "B");
In many cases, the compiler can infer the generic type automatically from the arguments, so you do not need to write it explicitly.
Generic Method with Multiple Type Parameters
A method can have more than one type parameter when the inputs and outputs involve different kinds of data.
public static void Display<TKey, TValue>(TKey key, TValue value)
{
Console.WriteLine($"{key} : {value}");
}
This pattern appears often in mapping, transformation, and reusable utility logic.
Generic Collections in C#
Many of the most important .NET collection types are generic. This is one of the biggest reasons generics matter so much in practice.
| Generic Collection | Purpose |
|---|---|
List<T> | Dynamic ordered collection |
Dictionary<TKey, TValue> | Key-value storage |
Queue<T> | First-in, first-out processing |
Stack<T> | Last-in, first-out processing |
HashSet<T> | Unique values collection |
These types are more type-safe and easier to use than old non-generic collections because they avoid repeated casting and make the data model explicit.
Generics vs object in C#
Before generics became common, reusable code often used object. That worked, but it forced developers to cast values back to their original types. That creates risk because the wrong cast may fail at runtime.
object value = 100;
int number = (int)value;
With generics, the compiler knows the type ahead of time, which reduces runtime surprises and improves readability.
| Point | Generics | object-based design |
|---|---|---|
| Type safety | Compile-time | Weaker, depends on casts |
| Casting | Usually not needed | Often required |
| Readability | Clear expected type | Less explicit |
| Runtime errors | Reduced | More likely from invalid casts |
Generic Constraints in C#
Sometimes a generic type should not accept every possible type. Constraints let you limit what kinds of types are valid for a generic parameter.
public class Repository<T> where T : class
{
}
This constraint means T must be a reference type. Common constraints include class, struct, new(), base-class constraints, and interface constraints.
| Constraint | Meaning |
|---|---|
where T : class | T must be a reference type |
where T : struct | T must be a value type |
where T : new() | T must have a public parameterless constructor |
where T : BaseType | T must inherit from a base class |
where T : IInterface | T must implement an interface |
Constraints make generic code more meaningful because they let you describe the rules the type must satisfy.
Generic Interface in C#
Interfaces can also be generic. This is common when the abstraction itself depends on the type being processed.
public interface IRepository<T>
{
void Add(T item);
T GetById(int id);
}
Generic interfaces are widely used in repositories, services, handlers, validators, and pipeline-style architectures.
Generic Return Types
A generic method can also return a value of the generic type parameter.
public static T CreateDefault<T>()
{
return default;
}
This is useful in reusable helpers, factories, and infrastructure code where the caller decides the result type.
Benefits of Generics in C#
- Improves code reuse by writing the logic once.
- Provides compile-time type safety.
- Reduces runtime casting errors.
- Makes APIs clearer because the expected type is explicit.
- Often improves performance compared with object-based designs by reducing boxing and unboxing in many cases.
Generics and Performance
Generics can help performance because they reduce the need to box value types into object. Boxing creates extra overhead and can lead to unnecessary allocations in some scenarios. Generic collections like List<int> store integers directly in a type-safe way instead of pushing everything through object.
This does not mean generics are only a performance feature. Their main strength is design clarity and type safety, but the performance benefits are an important practical advantage.
Common Mistakes with Generics
- Using
objectwhen a generic parameter would express the design more clearly. - Adding unnecessary generic complexity where a normal type would be simpler.
- Forgetting to apply constraints when the logic depends on certain capabilities.
- Choosing vague type parameter names in public APIs.
- Thinking generics remove the need for clear domain modeling.
Generics are powerful, but they should improve the design rather than make it harder to understand. Good naming and clear intent matter.
Best Practices for Generics in C#
- Use generics when the same logic should work across multiple types.
- Apply constraints when the generic parameter must satisfy specific rules.
- Prefer meaningful type parameter names such as
TKey,TValue, andTResultwhen one-letter names are not descriptive enough. - Avoid adding generics where a concrete type would be simpler and clearer.
- Use generic collections instead of old non-generic collections in modern C# code.
Generics in Real Applications
In real applications, generics appear in repositories, API response wrappers, result types, caching layers, validators, handlers, queue processing, and collection libraries. They are not just an academic language feature. They are one of the core tools for writing reusable framework-like code and clean application infrastructure.
If your code keeps repeating the same pattern for different types, generics are often the first design tool to consider.
Generics Interview Points
For interviews, remember that generics allow classes and methods to work with different data types while preserving type safety. You should know generic classes, generic methods, generic collections, constraints, and the difference between generics and object-based designs.
It is also useful to explain why generics help both readability and performance, especially by reducing casting and boxing in many common cases.
FAQs on Generics in C#
What are generics in C#?
Generics are a C# feature that allow classes, methods, interfaces, and collections to work with different types while keeping compile-time type safety.
Why are generics better than using object?
Generics avoid unnecessary casting, reduce runtime type errors, and make APIs clearer because the expected data types are explicit.
What is a generic constraint?
A generic constraint limits what kinds of types can be used with a generic parameter, such as only reference types, only value types, or only types implementing a certain interface.
Where are generics used in real C# code?
Generics are used in collections, repositories, services, result wrappers, caching, validation, tasks, and many reusable framework-style components.
Type Inference in Generic Methods
One reason generic methods feel natural in C# is type inference. In many cases, the compiler can determine the generic type from the arguments, so the caller does not have to specify it manually. This keeps the method call short while still preserving strong type checking.
That balance between flexibility and readability is one of the biggest strengths of generics in application code.
Multiple Type Parameters in Real Design
Many real APIs use more than one type parameter because the input and output roles are different. Examples include key-value collections, transformation pipelines, request-response wrappers, and mapper utilities. Names such as TKey, TValue, TSource, and TResult make those roles clearer than using only single-letter placeholders everywhere.
Constraints as Design Contracts
Constraints are not just compiler syntax. They act as design contracts. If your generic logic needs to create a new object, compare values, or call members from an interface, the constraint documents that requirement directly in the API. That makes the generic type safer and easier for other developers to understand and use correctly.