Generics in C# allow you to define classes, methods, delegates, and interfaces with a placeholder for the type of data they store or use. This enables you to create more flexible and reusable code. Generics provide type safety without the need for boxing or unboxing, which can improve performance.
Key Concepts
- Generic Classes: Classes that can operate on any data type.
- Generic Methods: Methods that can operate on any data type.
- Generic Interfaces: Interfaces that can be implemented by any data type.
- Constraints: Restrictions that can be applied to the types that can be used with generics.
Generic Classes
A generic class is defined with a type parameter. This type parameter can be used within the class to define the type of its members.
Example
public class GenericList<T> { private T[] elements; private int count = 0; public GenericList(int capacity) { elements = new T[capacity]; } public void Add(T element) { if (count < elements.Length) { elements[count] = element; count++; } } public T GetElement(int index) { if (index < count) { return elements[index]; } throw new IndexOutOfRangeException(); } }
Explanation
T
is the type parameter.elements
is an array of typeT
.- The
Add
method adds an element of typeT
to the list. - The
GetElement
method returns an element of typeT
from the list.
Generic Methods
A generic method is defined with a type parameter. This type parameter can be used within the method to define the type of its parameters and return value.
Example
public class Utilities { public static void Swap<T>(ref T a, ref T b) { T temp = a; a = b; b = temp; } }
Explanation
T
is the type parameter.- The
Swap
method swaps the values of two variables of typeT
.
Generic Interfaces
A generic interface is defined with a type parameter. This type parameter can be used within the interface to define the type of its members.
Example
Explanation
T
is the type parameter.- The
Add
method adds an item of typeT
to the repository. - The
Get
method returns an item of typeT
from the repository.
Constraints
Constraints can be applied to type parameters to restrict the types that can be used with generics.
Example
public class GenericList<T> where T : IComparable<T> { private T[] elements; private int count = 0; public GenericList(int capacity) { elements = new T[capacity]; } public void Add(T element) { if (count < elements.Length) { elements[count] = element; count++; } } public T GetElement(int index) { if (index < count) { return elements[index]; } throw new IndexOutOfRangeException(); } }
Explanation
where T : IComparable<T>
is a constraint that restrictsT
to types that implement theIComparable<T>
interface.
Practical Exercises
Exercise 1: Create a Generic Stack
Create a generic stack class that supports the following operations:
Push
: Adds an element to the top of the stack.Pop
: Removes and returns the element at the top of the stack.Peek
: Returns the element at the top of the stack without removing it.
Solution
public class GenericStack<T> { private T[] elements; private int count = 0; public GenericStack(int capacity) { elements = new T[capacity]; } public void Push(T element) { if (count < elements.Length) { elements[count] = element; count++; } else { throw new InvalidOperationException("Stack is full"); } } public T Pop() { if (count > 0) { count--; return elements[count]; } throw new InvalidOperationException("Stack is empty"); } public T Peek() { if (count > 0) { return elements[count - 1]; } throw new InvalidOperationException("Stack is empty"); } }
Exercise 2: Implement a Generic Repository
Create a generic repository class that supports the following operations:
Add
: Adds an item to the repository.Get
: Returns an item from the repository by its ID.
Solution
public class GenericRepository<T> where T : IEntity { private List<T> items = new List<T>(); public void Add(T item) { items.Add(item); } public T Get(int id) { return items.FirstOrDefault(item => item.Id == id); } } public interface IEntity { int Id { get; set; } }
Common Mistakes and Tips
- Type Safety: Always ensure that the type parameter constraints are correctly defined to avoid runtime errors.
- Boxing and Unboxing: Avoid unnecessary boxing and unboxing by using generics instead of non-generic collections.
- Performance: Generics can improve performance by reducing the need for type casting and boxing/unboxing.
Conclusion
Generics are a powerful feature in C# that allow you to create flexible, reusable, and type-safe code. By understanding and utilizing generic classes, methods, interfaces, and constraints, you can write more efficient and maintainable code. In the next module, we will explore collections in C#, which often make use of generics to provide type-safe data structures.
C# Programming Course
Module 1: Introduction to C#
- Introduction to C#
- Setting Up the Development Environment
- Hello World Program
- Basic Syntax and Structure
- Variables and Data Types
Module 2: Control Structures
Module 3: Object-Oriented Programming
- Classes and Objects
- Methods
- Constructors and Destructors
- Inheritance
- Polymorphism
- Encapsulation
- Abstraction
Module 4: Advanced C# Concepts
- Interfaces
- Delegates and Events
- Generics
- Collections
- LINQ (Language Integrated Query)
- Asynchronous Programming
Module 5: Working with Data
Module 6: Advanced Topics
- Reflection
- Attributes
- Dynamic Programming
- Memory Management and Garbage Collection
- Multithreading and Parallel Programming