Practicing the SOLID principles is a great way to prepare for your technical assessment. Below, I’ll explain each principle with C# examples, plus tips on how to approach them in an interview.

SOLID Principles in C#
The SOLID principles help in designing scalable, maintainable, and testable code.

Principle Description
S - Single Responsibility Principle
A class should have only one reason to change.

O - Open/Closed Principle

Code should be open for extension, but closed for modification.

L - Liskov Substitution Principle

Subclasses should be replaceable without breaking parent class functionality.

I - Interface Segregation Principle
Clients shouldn't be forced to implement unnecessary methods.

D - Dependency Inversion Principle

Depend on abstractions, not concretions.

🟢 1. Single Responsibility Principle (SRP)
✅ One class should have only one reason to change.
⛔ Bad Example: A class that does multiple things

csharp

public class Report
{
    public void GenerateReport() { /* Logic for report generation */ }
    public void SaveToFile() { /* Logic for saving to file */ }
}

This class violates SRP because it both generates and saves a report.

If saving logic changes, we must modify this class, breaking SRP.

✅ Good Example: Separate Responsibilities

csharp

public class ReportGenerator
{
    public string GenerateReport() => "Report Data";
}

public class ReportSaver
{
    public void SaveToFile(string reportData) { /* Logic to save */ }
}

Now, each class has only one responsibility.

Benefits: Easier to maintain and modify without affecting other responsibilities.

💡 Interview Tip:
If given a multi-purpose class, split responsibilities into separate classes.

🟢 2. Open/Closed Principle (OCP)
✅ Code should be open for extension but closed for modification.
⛔ Bad Example: Adding new functionality by modifying existing code

csharp

public class PaymentProcessor
{
    public void ProcessPayment(string paymentType)
    {
        if (paymentType == "CreditCard") { /* Process Credit Card */ }
        else if (paymentType == "PayPal") { /* Process PayPal */ }
    }
}

Adding a new payment method requires modifying this class.

✅ Good Example: Use Polymorphism for Extension

csharp

public interface IPaymentMethod
{
    void Pay();
}

public class CreditCardPayment : IPaymentMethod
{
    public void Pay() { /* Process Credit Card */ }
}

public class PayPalPayment : IPaymentMethod
{
    public void Pay() { /* Process PayPal */ }
}

public class PaymentProcessor
{
    public void ProcessPayment(IPaymentMethod paymentMethod) => paymentMethod.Pay();
}

New payment methods can be added without modifying PaymentProcessor.

💡 Interview Tip:
If asked to add new functionality, avoid modifying existing code—use abstraction and interfaces instead.

🟢 3. Liskov Substitution Principle (LSP)
✅ Subclasses should be replaceable without breaking parent class behavior.
⛔ Bad Example: Subclass breaks behavior

csharp

public class Rectangle
{
    public virtual void SetWidth(int width) { }
    public virtual void SetHeight(int height) { }
}

public class Square : Rectangle
{
    public override void SetWidth(int width) { /* Forces width and height to be same */ }
    public override void SetHeight(int height) { /* Forces width and height to be same */ }
}

A Square cannot behave like a Rectangle, breaking LSP.

✅ Good Example: Avoid Inheritance When It Doesn’t Fit

csharp

public interface IShape
{
    int GetArea();
}

public class Rectangle : IShape
{
    public int Width { get; set; }
    public int Height { get; set; }
    public int GetArea() => Width * Height;
}

public class Square : IShape
{
    public int SideLength { get; set; }
    public int GetArea() => SideLength * SideLength;
}

Both Rectangle and Square follow LSP and can be used interchangeably.

💡 Interview Tip:
If a subclass changes the expected behavior, rethink inheritance and consider using interfaces.

🟢 4. Interface Segregation Principle (ISP)
✅ Interfaces should be small and specific, not forcing unused methods.
⛔ Bad Example: Large interfaces forcing unnecessary implementations

csharp

public interface IWorker
{
    void Work();
    void Eat();
}

public class Robot : IWorker
{
    public void Work() { /* Can work */ }
    public void Eat() { throw new NotImplementedException(); } // ❌ Robots don't eat
}

✅ Good Example: Split Interfaces

csharp

public interface IWorkable { void Work(); }
public interface IEatable { void Eat(); }

public class Robot : IWorkable
{
    public void Work() { /* Works without eating */ }
}

public class Human : IWorkable, IEatable
{
    public void Work() { /* Works */ }
    public void Eat() { /* Eats */ }
}

Now, only relevant methods are implemented.

💡 Interview Tip:
If an interface has too many methods, split it into multiple specific interfaces.

🟢 5. Dependency Inversion Principle (DIP)
✅ High-level modules should not depend on low-level modules. Both should depend on abstractions.
⛔ Bad Example: High-level module directly depending on low-level module

csharp

public class EmailService
{
    public void SendEmail() { /* Sends Email */ }
}

public class Notification
{
    private EmailService emailService = new EmailService();

    public void Send() { emailService.SendEmail(); } // ❌ Hard dependency
}

Notification is tightly coupled to EmailService.

✅ Good Example: Depend on an Abstraction

csharp

public interface IMessageService
{
    void Send();
}

public class EmailService : IMessageService
{
    public void Send() { /* Sends Email */ }
}

public class Notification
{
    private readonly IMessageService _messageService;

    public Notification(IMessageService messageService)
    {
        _messageService = messageService;
    }

    public void Send() { _messageService.Send(); }
}

Now, Notification can work with any message service (Email, SMS, etc.).

💡 Interview Tip:
If a class directly creates instances of dependencies, refactor it using Dependency Injection.

🔹 Final Advice for Your Assessment
Understand the problem before coding.

Think in OOP—use abstractions, interfaces, and classes wisely.

Keep classes focused—follow Single Responsibility Principle.

Use Dependency Injection—avoid tight coupling.

If modifying existing code, check if you can extend instead.