C# Design Patterns & Best Practices - Textnotes

C# Design Patterns & Best Practices


Design patterns and best practices help write maintainable, reusable, and efficient C# code.

1. Singleton Pattern

  1. Ensures only one instance of a class exists.
  2. 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

  1. Creates objects without exposing the creation logic.
  2. 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

  1. Abstracts data access logic from business logic.
  2. 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)

  1. Promotes loose coupling between classes.
  2. 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 ResponsibilityA class should have only one reason to change.
O - Open/ClosedClasses should be open for extension, closed for modification.
L - Liskov SubstitutionSubtypes must replace base types without breaking code.
I - Interface SegregationPrefer many small interfaces over a large general-purpose interface.
D - Dependency InversionDepend on abstractions, not concrete implementations.

6. Code Optimization Tips

  1. Use StringBuilder for string concatenation in loops.
  2. Minimize LINQ queries in tight loops.
  3. Dispose unmanaged resources with using.
  4. Avoid boxing/unboxing in performance-critical code.
  5. Use async/await for IO-bound operations.
  6. Use object pooling for frequently created objects.
  7. Profile code using dotTrace, Visual Studio Profiler.

Summary of Chapter 18:

  1. Singleton, Factory, Repository: Common design patterns for structured and reusable code.
  2. Dependency Injection: Enables loose coupling and testable code.
  3. SOLID Principles: Ensure maintainable and scalable architecture.
  4. Code Optimization: Improve performance with proper practices.