C# Design Patterns & Best Practices
Design patterns and best practices help write maintainable, reusable, and efficient C# code.
1. Singleton Pattern
- Ensures only one instance of a class exists.
- Useful for configuration, logging, or caching.
public class Singleton
{
private static Singleton _instance;
private static readonly object _lock = new object();
private Singleton() { }
public static Singleton Instance
{
get
{
lock (_lock)
{
if (_instance == null)
_instance = new Singleton();
return _instance;
}
}
}
public void ShowMessage() => Console.WriteLine("Singleton instance called");
}
Usage:
Singleton.Instance.ShowMessage();
2. Factory Pattern
- Creates objects without exposing the creation logic.
- Useful when you want flexible object creation.
public interface IShape { void Draw(); }
public class Circle : IShape
{
public void Draw() => Console.WriteLine("Drawing Circle");
}
public class Rectangle : IShape
{
public void Draw() => Console.WriteLine("Drawing Rectangle");
}
public class ShapeFactory
{
public static IShape GetShape(string shapeType)
{
return shapeType.ToLower() switch
{
"circle" => new Circle(),
"rectangle" => new Rectangle(),
_ => null
};
}
}
Usage:
var shape = ShapeFactory.GetShape("circle");
shape.Draw();
3. Repository Pattern
- Abstracts data access logic from business logic.
- Makes code easier to maintain and test.
public interface IStudentRepository
{
IEnumerable<Student> GetAll();
void Add(Student student);
}
public class StudentRepository : IStudentRepository
{
private readonly List<Student> _students = new();
public IEnumerable<Student> GetAll() => _students;
public void Add(Student student) => _students.Add(student);
}
Usage:
IStudentRepository repo = new StudentRepository();
repo.Add(new Student { Name = "John", Age = 20 });
4. Dependency Injection (DI)
- Promotes loose coupling between classes.
- Inject dependencies via constructor or property.
public interface IMessageService { void Send(string msg); }
public class EmailService : IMessageService
{
public void Send(string msg) => Console.WriteLine("Email: " + msg);
}
public class Notification
{
private readonly IMessageService _service;
public Notification(IMessageService service) => _service = service;
public void Notify(string message) => _service.Send(message);
}
// Usage
var emailService = new EmailService();
var notification = new Notification(emailService);
notification.Notify("Hello!");
5. SOLID Principles
| PrincipleDescription | |
| S - Single Responsibility | A class should have only one reason to change. |
| O - Open/Closed | Classes should be open for extension, closed for modification. |
| L - Liskov Substitution | Subtypes must replace base types without breaking code. |
| I - Interface Segregation | Prefer many small interfaces over a large general-purpose interface. |
| D - Dependency Inversion | Depend on abstractions, not concrete implementations. |
6. Code Optimization Tips
- Use StringBuilder for string concatenation in loops.
- Minimize LINQ queries in tight loops.
- Dispose unmanaged resources with
using. - Avoid boxing/unboxing in performance-critical code.
- Use async/await for IO-bound operations.
- Use object pooling for frequently created objects.
- Profile code using dotTrace, Visual Studio Profiler.
Summary of Chapter 18:
- Singleton, Factory, Repository: Common design patterns for structured and reusable code.
- Dependency Injection: Enables loose coupling and testable code.
- SOLID Principles: Ensure maintainable and scalable architecture.
- Code Optimization: Improve performance with proper practices.