C# Anti-patterns, and what to do about them?
Here are ten common C# anti-patterns. For each, you’ll find:
What it is
A minimal code example
Why it’s harmful
What to do instead
1. God Object
There is no reason to code like you are working on COBOL in the 1980s.
What it is:
A single class that knows or does too much (violates Single Responsibility).
public class ApplicationManager
{
public void LoadConfig()
{
/*…*/
}
public void RenderUI()
{ /*…*/ }
public void ConnectDb()
{ /*…*/ }
public void SendEmail()
{ /*…*/ }
// …dozens more methods…
}Why it’s harmful:
Becomes huge and hard to understand
Changes ripple through unrelated functionality
Hard to unit-test
Better:
Break into focused services, e.g. ConfigLoader, UiRenderer, EmailSender, each with a clear interface.
2. Spaghetti Code
There is no reason to code like you are working on Apple BASIC in the 1980s.
What it is:
Tangled control flow with goto-like jumps, deep nesting, or unstructured callbacks.
void Process()
{
if (cond1)
{
// …
if (cond2) goto LabelA;
// …
}
LabelA: // …
}Why it’s harmful:
Impossible to trace logic
High risk of hidden bugs
Better:
Use well-structured methods, early returns, and clear state machines or pipelines.
3. Magic Strings & Numbers
C# has had Enums since day 1, due to its JAVA origins. Act like it.
What it is:
Embedding literals directly in code.
if (user.Role == "Admin")
{
// Do something
}
Why it’s harmful:
Typos cause silent bugs
Hard to update or reuse
No semantic meaning
Better:
Use enum or named constants:
public enum Role {
User,
Admin,
Guest
}
if (user.Role == Role.Admin)
{
// Do something
} 4. Empty Catch Block
If you aren’t catching anything, then why are you in the game?
What it is:
Catching exceptions and ignoring them.
try
{
DoRiskyWork();
}
catch (Exception)
{
// Do nothing
}Why it’s harmful:
Silently hides errors
Makes debugging impossible
Better:
Catch only expected exceptions, at minimum log or rethrow:
catch (IOException ex)
{
logger.LogError(ex, "I/O failed");
throw;
}5. Primitive Obsession
You not caveman, you code should not be too.
What it is:
Using primitives (e.g. string, int) for domain concepts.
void ScheduleMeeting(string date, string time, string WithPerson)
{
// do something
}Why it’s harmful:
No invariants or validation
Scatters parsing/formatting logic
Better:
Create value types or structs:
public Struct Invite {
public DateTime meeting {get; set;}
public String WithPerson {get; set;}
}
public ScheduleMeeting(Invite MeetingRequest)
{
//do something
}6. Tight Coupling
Smart constructors make smarter code.
What it is:
Classes directly instantiate or depend on concrete types.
public class OrderService
{
private readonly EmailSender _mailer = new EmailSender();
// …
}Why it’s harmful:
Hard to swap or mock dependencies
Violates Dependency Inversion
Better:
Inject abstractions via interfaces:
public OrderService(IEmailSender mailer)
{
//Do something
}7. Copy-Paste Programming
We know that you copy and paste from Google, so let’s see you implement that code intelligently.
What it is:
Duplicated logic is scattered across the code.
// File A
var total = items.Sum(i => i.Price) * 1.1m;
// File B (same code duplicated)
var total = items.Sum(i => i.Price) * 1.1m;Why it’s harmful:
Bug fixes must be applied in many places
Increases maintenance cost
Better:
Extract shared logic into a single helper or service. Putting a common function into a static class with an ‘internal’ declaration is also acceptable.
8. Overuse of async void
What it is:
Using async void for anything other than event handlers.
async void DoWorkAsync()
{
await Task.Delay(1000);
}Why it’s harmful:
Uncatchable exceptions
No way to await completion
Better:
Use async Task everywhere:
async Task DoWorkAsync()
{
await Task.Delay(1000);
}9. Singleton Abuse
Your code is not cheating on you when you have properly managed instances.
What it is:
Global state via singletons—even when not warranted.
public class Config {
private static Config _instance;
public static Config Instance => _instance
??= new Config();
// …
}Why it’s harmful:
Hidden dependencies
Hard to test or extend
Lifecycle management issues
Better:
Use DI container to manage lifetimes explicitly.
10. Anemic Domain Model
You think low iron is bad for humans, just wait until you see it in code.
What it is:
Domain objects hold only data; behavior lives elsewhere.
class Customer {
public string Name { get; set; }
public decimal Balance { get; set; }
}
// Business logic in service:
void Charge(Customer c, decimal amt)
{
c.Balance -= amt;
}Why it’s harmful:
Loses object-oriented benefits
Logic duplication across services
Better:
Encapsulate behavior in the model:
class Customer
{
public void Charge(decimal amount) {
if (amount > Balance)
throw new InvalidOperationException();
Balance -= amount;
}
}By recognizing these anti-patterns—and replacing them with focused, maintainable designs—you’ll make your C# code more robust, testable, and clear.
Credit: OpenAI’s ChatGPT 4.0, using my account. 😉

