Single Responsibility Principle in C#: Writing Classes That Do One Thing Well
Let’s be honest—most of us have, at some point, crammed way too much into a single class. We start out with a nice little InvoiceManager, and before long it’s calculating taxes, saving to disk, sending emails, and making coffee. It works… until it doesn’t. That’s where the Single Responsibility Principle (SRP) comes in—and it’s more than a fancy acronym. It's the “S” in SOLID, and it’s all about one thing: A class should have only one reason to change. What That Actually Means If a class has multiple responsibilities, it has multiple reasons to change. That might not sound dangerous, but it leads to brittle code, mysterious bugs, and update anxiety. The moment you change the invoice saving logic, you risk breaking the calculation. Update the formatting? Hope it doesn’t affect that PDF export. Sound familiar? SRP says: don’t mix concerns. Let each class focus on doing one thing really well. A Common Example in C Let’s say we’re working with invoices. Here's something we might find in a typical project: public class InvoiceProcessor { public void CalculateTotal(Invoice invoice) { // Logic to calculate total } public void SaveToFile(Invoice invoice) { // Logic to save invoice } public void PrintInvoice(Invoice invoice) { // Logic to print invoice } } This looks neat at first glance, but the cracks show fast. This one class is doing: Business logic (calculating totals) I/O (saving to file) Presentation (printing) That’s three separate responsibilities, and that means three reasons to change. Not great. ✂️ Let’s Refactor It Following SRP, we break this up into smaller, focused classes: public class InvoiceCalculator { public void CalculateTotal(Invoice invoice) { // Calculation logic } } public class InvoiceSaver { public void SaveToFile(Invoice invoice) { // File-saving logic } } public class InvoicePrinter { public void Print(Invoice invoice) { // Printing logic } } Now we’ve got one job per class, and everything is easier: Testing is simpler. Changes are isolated. Reuse becomes possible. “But Now I Have Too Many Classes…” Yes—and that’s a good thing. Think of it like this: Would you prefer one kitchen drawer jammed with tools, or a set of organized drawers where everything has its place? More classes can feel like overhead at first, but they pay off in readability, clarity, and peace of mind. You’ll know where things are. And so will your teammates.

Let’s be honest—most of us have, at some point, crammed way too much into a single class.
We start out with a nice little InvoiceManager
, and before long it’s calculating taxes, saving to disk, sending emails, and making coffee. It works… until it doesn’t.
That’s where the Single Responsibility Principle (SRP) comes in—and it’s more than a fancy acronym. It's the “S” in SOLID, and it’s all about one thing:
A class should have only one reason to change.
What That Actually Means
If a class has multiple responsibilities, it has multiple reasons to change. That might not sound dangerous, but it leads to brittle code, mysterious bugs, and update anxiety.
The moment you change the invoice saving logic, you risk breaking the calculation. Update the formatting? Hope it doesn’t affect that PDF export. Sound familiar?
SRP says: don’t mix concerns. Let each class focus on doing one thing really well.
A Common Example in C
Let’s say we’re working with invoices. Here's something we might find in a typical project:
public class InvoiceProcessor
{
public void CalculateTotal(Invoice invoice)
{
// Logic to calculate total
}
public void SaveToFile(Invoice invoice)
{
// Logic to save invoice
}
public void PrintInvoice(Invoice invoice)
{
// Logic to print invoice
}
}
This looks neat at first glance, but the cracks show fast.
This one class is doing:
- Business logic (calculating totals)
- I/O (saving to file)
- Presentation (printing)
That’s three separate responsibilities, and that means three reasons to change. Not great.
✂️ Let’s Refactor It
Following SRP, we break this up into smaller, focused classes:
public class InvoiceCalculator
{
public void CalculateTotal(Invoice invoice)
{
// Calculation logic
}
}
public class InvoiceSaver
{
public void SaveToFile(Invoice invoice)
{
// File-saving logic
}
}
public class InvoicePrinter
{
public void Print(Invoice invoice)
{
// Printing logic
}
}
Now we’ve got one job per class, and everything is easier:
- Testing is simpler.
- Changes are isolated.
- Reuse becomes possible.
“But Now I Have Too Many Classes…”
Yes—and that’s a good thing.
Think of it like this: Would you prefer one kitchen drawer jammed with tools, or a set of organized drawers where everything has its place?
More classes can feel like overhead at first, but they pay off in readability, clarity, and peace of mind.
You’ll know where things are. And so will your teammates.