Generics
Write once, use with any type — the foundation of type-safe reusable code in C#
1. What are Generics?
Generics allow you to write reusable, type-safe code that works with different data types without losing compile-time type checking.
public class Box<T>
{
public T Value { get; set; }
}
Here, T is a placeholder for the actual type.
Box<T>
↓
T = int → Box<int>
T = string → Box<string>
Why Use Generics?
- ♻️ Reusable — One implementation works with many types.
- 🔒 Type-safe — Compiler checks types at compile time.
- 🚫 Fewer casts — No need to repeatedly cast from
object. - ⚡ Better performance — Avoid boxing/unboxing for value types.
2. Generic Classes
var intBox = new Box<int>();
intBox.Value = 10;
var stringBox = new Box<string>();
stringBox.Value = "Hello";
⭐ Key Concept:
T is not a specific type. It is a type parameter that represents the actual type supplied by the caller.
3. Multiple Type Parameters
public class Pair<T1, T2>
{
public T1 First { get; set; }
public T2 Second { get; set; }
}
var pair = new Pair<int, string>
{
First = 101,
Second = "Jithin"
};
4. Generic Methods
public static void Print<T>(T value)
{
Console.WriteLine(value);
}
// Usage
Print(100); // T = int
Print("Hello"); // T = string
Print(DateTime.Now); // T = DateTime
5. Generic Type Inference
Helper.Print<int>(100); // Explicit
Helper.Print(100); // Inferred — compiler determines T = int
6. Generic Constraints
Constraints do two important things:
- Restrict which types can be used as
T - Give the compiler guarantees about what
Tcan do
7. class Constraint
public void Process<T>(T value)
where T : class
{
}
Process("Hello"); // ✅ string is reference type
Process(10); // ❌ int is value type
8. struct Constraint
public void Process<T>(T value)
where T : struct
{
}
Process(10); // ✅ int is value type
Process("Hello"); // ❌ string is reference type
9. Interface Constraint
public void Save<T>(T entity)
where T : IEntity
{
Console.WriteLine(entity.Id); // Safe — compiler knows T has Id
}
⭐ Key Concept: The constraint gives the compiler a guarantee.
where T : IEntity means "Whatever T is, it will implement IEntity."
10. new() Constraint
public T Create<T>()
where T : new()
{
return new T();
}
var customer = Create<Customer>();
Important Rule: When combined with other constraints, new() must appear last.
11. Multiple Constraints
public void Process<T>()
where T : class, IEntity, new()
{
T entity = new T();
Console.WriteLine(entity.Id);
}
The declaration where T : class, IEntity, new() means T must satisfy all three requirements simultaneously:
- class → T must be a reference type (not a value type like int or double)
- IEntity → T must implement the IEntity interface (giving the compiler access to all members defined by IEntity)
- new() → T must have a public parameterless constructor (so the code can safely create instances with new T())
12. Constraint Ordering
1. class / struct
2. Base class
3. Interface
4. new()
13. Why Do Constraints Matter?
Without a constraint:
public void Save<T>(T entity)
{
Console.WriteLine(entity.Id); // ❌ Compiler doesn't know if T has Id
}
With a constraint:
public void Save<T>(T entity)
where T : IEntity
{
Console.WriteLine(entity.Id); // ✅ Compiler knows T implements IEntity
}
⭐ Key Principle: A generic constraint gives the compiler enough information to safely use the members guaranteed by that constraint.
14. Common Built-in Generic Types
| Type | Example |
|---|---|
List<T> | List<int> numbers = new(); |
Dictionary<TKey, TValue> | Dictionary<int, string> users = new(); |
IEnumerable<T> | IEnumerable<Customer> customers = ...; |
Nullable<T> / T? | int? age = null; |
15. Generics vs. object
| Feature | object | Generics |
|---|---|---|
| Compile-time type safety | Limited | ✅ Strong |
| Explicit casting | Often required | Usually not required |
| Runtime type errors | More possible | Reduced |
| Type clearly expressed | ❌ | ✅ |
16. Generic Repository Example
public class Repository<T>
where T : class, IEntity, new()
{
public T Create()
{
return new T();
}
}
// Usage
var repository = new Repository<Customer>();
Customer customer = repository.Create();
17. Interview Takeaways ⭐
Generics
Generics → Reusable, strongly typed code.
T
T → Type parameter, not a specific type.
Generic Method
A method can have its own type parameter: Print<T>(T value)
where T : class
T must be a reference type.
where T : struct
T must be a non-nullable value type.
where T : IEntity
T must implement IEntity.
where T : new()
T must have a public parameterless constructor.
18. Quick Revision
| Concept | Remember |
|---|---|
| Generics | Reusable, type-safe code |
| T | Type parameter |
| Generic Class | Class with type parameters |
| Generic Method | Method with its own type parameter |
| Type Inference | Compiler determines T from arguments |
| where T : class | Reference type |
| where T : struct | Non-nullable value type |
| where T : IEntity | Must implement interface |
| where T : new() | Public parameterless constructor |
| new() ordering | Must be last |
19. One-Line Interview Answers ⭐
Generics allow us to write reusable, type-safe code that works with different data types while maintaining compile-time type checking.
T is a type parameter that represents the actual type supplied when a generic class or method is used.
A generic constraint restricts the types that can be used as a type parameter and gives the compiler guarantees about the capabilities of that type.
Generics provide compile-time type safety, reduce explicit casting, and can avoid boxing and unboxing for value types.