1. What are generics in C#, and what are their advantages?
What are Generics?
Generics allow us to create reusable classes, methods, interfaces, and collections that work with different data types while maintaining compile-time type safety.
Instead of writing separate code for int, string, or other types, we can use a type parameter (T).
The actual data type is specified when using the generic class or method.
Example Program
Without Generics:
public class IntBox
{
public int Value { get; set; }
}
public class StringBox
{
public string Value { get; set; } = "";
}
We need separate classes for different data types.
With Generics:
public class Box<T>
{
public T Value { get; set; }
public Box(T value)
{
Value = value;
}
public void Display()
{
Console.WriteLine(Value);
}
}
// Program
Box<int> intBox = new(100);
intBox.Display();
Box<string> stringBox = new("Hello");
stringBox.Display();
Box<double> doubleBox = new(25.5);
doubleBox.Display();
Output:
100
Hello
25.5
Program Explanation
-
Box<T>defines a generic class. -
Trepresents a data type that will be provided later. -
Box<int>replacesTwithint. -
Box<string>replacesTwithstring. -
Box<double>replacesTwithdouble. -
The same class works with multiple data types without duplicating code.
How Generics Work
Generic Class
Box<T>
|
+----------+----------+
| | |
v v v
Box<int> Box<string> Box<double>
| | |
v v v
100 "Hello" 25.5
One Generic Definition
Multiple Data Types
Advantages of Generics
|
Advantage |
Explanation |
|---|---|
|
Type safety |
Detects invalid types at compile time |
|
Code reusability |
Same code works with different types |
|
Performance |
Avoids unnecessary boxing/unboxing |
|
Maintainability |
Reduces duplicate code |
|
Strong typing |
Reduces runtime casting errors |
Key Points
-
Generics were introduced in C# 2.0.
-
Tis a placeholder for a data type. -
Generics support classes, methods, interfaces, delegates, and collections.
-
Common examples are
List<T>,Dictionary<TKey,TValue>, andIEnumerable<T>. -
Generics provide compile-time type safety.
Interview Tip: Generics improve code reusability, type safety, and performance, particularly when working with value types.
2. How do generics provide type safety and avoid boxing/unboxing?
Explanation
Generics provide two important benefits:
1. Type Safety
Generics ensure that only the specified data type can be used.
2. Avoid Boxing and Unboxing
Generic collections such as List<int> can store value types without converting them into object.
What is Boxing and Unboxing?
-
Boxing: Converting a value type into an
object, which involves allocating a boxed object. -
Unboxing: Extracting the value type from a boxed object through an explicit conversion.
Example Program
Without Generics:
using System.Collections;
ArrayList numbers = new();
numbers.Add(100); // Boxing
numbers.Add(200); // Boxing
numbers.Add("Hello"); // Allowed
int value = (int)numbers[0]; // Unboxing
Console.WriteLine(value);
Output:
100
Problems
-
ArrayListstores elements asobject. -
Adding integers requires boxing.
-
Retrieving integers requires unboxing.
-
Different types can be inserted into the collection.
-
Invalid casting can cause runtime exceptions.
For example:
int value = (int)numbers[2];
This throws InvalidCastException because the third element is a string.
With Generics
List<int> numbers = new();
numbers.Add(100);
numbers.Add(200);
// numbers.Add("Hello"); // Compile-time error
int value = numbers[0];
Console.WriteLine(value);
Output:
100
Program Explanation
-
List<int>accepts only integers. -
Adding a string causes a compile-time error.
-
Integers are stored without boxing each element.
-
Retrieving an integer does not require an explicit cast.
-
This improves type safety and avoids boxing overhead.
Internal Working
Without Generics:
ArrayList
|
v
Add(100)
|
v
Boxing
|
v
Object on Heap
|
v
Retrieve Value
|
v
Unboxing
|
v
int value = 100
With Generics:
List<int>
|
v
Add(100)
|
v
Store Integer Directly
|
v
Retrieve Integer
|
v
int value = 100
No Boxing/Unboxing Required
Key Points
-
Generics enforce compile-time type safety.
-
List<int>prevents inserting strings. -
Generic collections avoid boxing/unboxing when storing and retrieving value-type elements.
-
Boxing creates an object allocation.
-
Unboxing requires an explicit conversion.
-
Avoiding boxing reduces unnecessary memory allocations and garbage collection overhead.
Important: Generics do not eliminate boxing in every situation. Boxing can still occur if a value type is explicitly converted to object or a non-generic interface.
Interview Tip: List<int> is generally preferable to ArrayList because it provides strong typing and avoids boxing/unboxing for stored integers.
3. What is the difference between a generic class and a generic method?
Explanation
Both use type parameters, but their scope is different.
Generic Class: A class that defines one or more type parameters.
Generic Method: A method that defines its own type parameters, even if the containing class is not generic.
Comparison
|
Feature |
Generic Class |
Generic Method |
|---|---|---|
|
Type parameter declared at |
Class level |
Method level |
|
Scope |
Throughout the class |
Within the method |
|
Example |
|
|
|
Type specified |
When creating/using the class |
When calling the method |
|
Common use |
Reusable repositories |
Reusable utility methods |
Example Program
Generic Class:
public class DataStore<T>
{
private T _data;
public DataStore(T data)
{
_data = data;
}
public void Display()
{
Console.WriteLine(_data);
}
}
// Program
DataStore<int> store1 = new(100);
store1.Display();
DataStore<string> store2 = new("Hello");
store2.Display();
Output:
100
Hello
Here, T is defined at the class level and is available to its members.
Generic Method Example
public class Helper
{
public void Print<T>(T value)
{
Console.WriteLine(value);
}
}
// Program
Helper helper = new();
helper.Print<int>(100);
helper.Print<string>("Hello");
helper.Print<double>(25.5);
Output:
100
Hello
25.5
Program Explanation
-
Helperis a normal class. -
Print<T>()is a generic method. -
The method accepts different data types.
-
Each invocation can use a different type.
-
The compiler can infer the type from the argument.
For example:
helper.Print(100); // T = int
helper.Print("Hello"); // T = string
We do not always need to explicitly specify <T> when calling a generic method.
Internal Working
Generic Class
--------------------------
DataStore<T>
|
+-----+-----+
| |
v v
DataStore<int> DataStore<string>
| |
v v
100 "Hello"
Generic Method
--------------------------
Helper
|
v
Print<T>()
|
+-----+-----+
| |
v v
Print<int>() Print<string>()
| |
v v
100 "Hello"
Real-World Example
Generic classes are frequently used in repository patterns.
public class Repository<T>
{
public void Add(T entity)
{
Console.WriteLine($"Added: {entity}");
}
}
Usage:
Repository<string> repository = new();
repository.Add("Employee");
A generic method is useful for common operations such as logging or formatting values.
Key Points
-
Generic class type parameters are declared at class level.
-
Generic method type parameters are declared at method level.
-
A normal class can contain generic methods.
-
A generic class can also contain generic methods.
-
Generic methods often support type inference.
-
Generic classes are useful for repositories and reusable data structures.
Interview Tip: A generic class makes the entire class reusable across types, whereas a generic method makes a particular operation reusable across types.
4. What is a generic interface? Explain with an example.
What is a Generic Interface?
A generic interface defines a contract using one or more type parameters.
It allows different classes to implement the same contract for different data types.
Generic interfaces are commonly used in:
-
Repository patterns
-
Service layers
-
Dependency injection
-
Clean Architecture
-
Generic collections
Example Program
Suppose we want a common repository interface for different entities.
Step 1: Create a Generic Interface
public interface IRepository<T>
{
void Add(T entity);
T? GetById(int id);
}
T represents the entity type.
Step 2: Create Employee Class
public class Employee
{
public int Id { get; set; }
public string Name { get; set; } = "";
}
Step 3: Implement Generic Interface
public class EmployeeRepository : IRepository<Employee>
{
private readonly List<Employee> _employees = new();
public void Add(Employee employee)
{
_employees.Add(employee);
}
public Employee? GetById(int id)
{
return _employees.FirstOrDefault(e => e.Id == id);
}
}
Step 4: Use Repository
IRepository<Employee> repository = new EmployeeRepository();
repository.Add(new Employee
{
Id = 101,
Name = "John"
});
repository.Add(new Employee
{
Id = 102,
Name = "David"
});
Employee? employee = repository.GetById(102);
Console.WriteLine(employee?.Name);
Output:
David
Program Explanation
-
IRepository<T>defines a reusable contract. -
EmployeeRepositoryimplementsIRepository<Employee>. -
The generic type
TbecomesEmployee. -
Add()accepts only Employee objects. -
GetById()returns an Employee object or null. -
The caller works through the interface instead of depending directly on the implementation.
How Generic Interfaces Work
IRepository<T>
|
v
IRepository<Employee>
|
v
EmployeeRepository
|
+------+------+
| |
v v
Add(Employee) GetById(id)
| |
v v
Save Employee Return Employee
Real-World Scenario: ASP.NET Core Dependency Injection
We can register the repository with the DI container.
builder.Services.AddScoped<
IRepository<Employee>,
EmployeeRepository>();
Then inject it into a service.
public class EmployeeService
{
private readonly IRepository<Employee> _repository;
public EmployeeService(IRepository<Employee> repository)
{
_repository = repository;
}
public Employee? GetEmployee(int id)
{
return _repository.GetById(id);
}
}
This supports Dependency Inversion and makes the application easier to test.
Key Points
-
A generic interface defines a type-safe reusable contract.
-
IRepository<T>is a common example. -
Different implementations can use different entity types.
-
Generic interfaces support abstraction and dependency injection.
-
They are widely used in Clean Architecture.
-
Built-in examples include
IEnumerable<T>,ICollection<T>,IList<T>, andIComparer<T>.
Interview Tip: Generic interfaces are useful for designing reusable, loosely coupled application components, especially repositories and services.
5. What is the difference between generics and the object type?
Explanation
Both generics and object can be used to work with different data types.
However, they work differently.
object is the base type of all C# types and can hold values of different types.
Generics allow us to specify a particular type while retaining compile-time type safety.
Comparison
|
Feature |
Generics |
object |
|---|---|---|
|
Type safety |
Compile-time |
Requires runtime checks/casts |
|
Type parameter |
Uses |
Uses |
|
Casting |
Usually unnecessary |
Often required |
|
Boxing |
Avoided for many value-type operations |
Occurs for value-type conversion |
|
Performance |
Generally better for typed value operations |
Can have boxing/casting overhead |
|
Reusability |
Yes |
Yes |
|
Common use |
Collections, repositories |
Mixed-type values, general APIs |
Example Program
Using Object:
public class ObjectBox
{
public object Value { get; set; }
public ObjectBox(object value)
{
Value = value;
}
}
// Program
ObjectBox box = new(100);
int number = (int)box.Value;
Console.WriteLine(number);
Output:
100
Problems
-
The stored value is exposed as
object. -
Explicit casting is needed to retrieve an integer.
-
Boxing occurs when passing an integer to the constructor.
-
Incorrect casting can cause runtime exceptions.
Using Generics
public class GenericBox<T>
{
public T Value { get; set; }
public GenericBox(T value)
{
Value = value;
}
}
// Program
GenericBox<int> box = new(100);
int number = box.Value;
Console.WriteLine(number);
Output:
100
Program Explanation
-
GenericBox<int>specifies the type asint. -
The constructor accepts an integer.
-
The property returns an integer directly.
-
No explicit cast is needed.
-
No boxing is required for storing the integer in this generic property.
Internal Working
Using Object:
int value = 100
|
v
Convert to object
|
v
Boxing
|
v
ObjectBox.Value
|
v
Explicit Cast
|
v
Unboxing
|
v
int number = 100
Using Generics:
int value = 100
|
v
GenericBox<int>
|
v
Store as int
|
v
Retrieve as int
|
v
int number = 100
No Boxing or Explicit Cast
When should we use object?
object can still be useful when an API genuinely needs to handle unrelated types dynamically.
For example:
object value = "Hello";
if (value is string text)
{
Console.WriteLine(text);
}
However, when the expected type is known, generics are usually preferable.
Key Points
-
objectis the base type of all C# types. -
Generics use type parameters to provide strong typing.
-
Generics reduce runtime casting errors.
-
Generics avoid unnecessary boxing/unboxing for value types.
-
Use generics when the data type can be specified.
-
Use
objectwhen genuinely heterogeneous values are required.
Interview Tip: The main advantages of generics over object are compile-time type safety, reduced casting, and better performance for many value-type scenarios.
Quick Interview Revision
|
Question |
Important Answer |
|---|---|
|
What are generics? |
Reusable, type-safe code using type parameters |
|
When were generics introduced? |
C# 2.0 |
|
What does |
Generic type parameter |
|
Main advantages? |
Type safety, reusability, performance |
|
How do generics provide type safety? |
Compile-time type checking |
|
How do generics avoid boxing? |
Store value types without converting to object |
|
Generic class? |
Class with type parameters |
|
Generic method? |
Method with its own type parameters |
|
Can a normal class have generic methods? |
Yes |
|
Can generic methods infer types? |
Yes, when arguments provide enough information |
|
Generic interface? |
Interface with type parameters |
|
Example of generic interface? |
|
|
Built-in generic interfaces? |
|
|
Generics vs object? |
Compile-time typing vs general object references |
|
Are generics always faster? |
No; performance depends on usage |
Final Comparison
Generics in C#
|
+-------------+-------------+
| | |
v v v
Generic Class Generic Method Generic Interface
| | |
v v v
Repository<T> Print<T>() IRepository<T>
| | |
+-------------+-------------+
|
v
Type Safety
|
v
Code Reusability
|
v
Reduced Boxing/Casting
Most important for interviews: Understand why generics are preferred over object, how they prevent boxing/unboxing, the difference between generic classes and methods, and how generic interfaces such as IRepository<T> are used in real-world .NET applications.