Refactoring Conditional if in C#: Avoiding Conditional Clutter with Derek Comartin
In C#, conditional statements like the if statement, if else statement, and switch statement are essential tools. But what happens when these constructs are used excessively—especially tied to enums? Derek Comartin, in his video "Enums Aren’t Evil. Conditionals Everywhere Are", takes us through a detailed refactor that replaces widespread conditional logic with cleaner, more maintainable patterns.
In this article, we’ll walk through Derek’s reasoning step by step, using his timestamps as anchors. We’ll also explore how his ideas apply to common conditional patterns in C# like the ternary operator, else statement, and switch-case structures—highlighting where these can cause problems in large codebases and how you can refactor toward better design.
The Conditional If Explosion: The Real Problem
Derek starts by showcasing an if statement that checks the product type:
if (productType == ProductType.Template || productType == ProductType.Ebook)if (productType == ProductType.Template || productType == ProductType.Ebook)At first glance, the conditional if above looks straightforward. But Derek warns that this kind of statement evaluates a given condition and then executes a block of code only if the condition is true—which becomes problematic when this logic is repeated everywhere.
You might have this block again in another method or class:
if (offeringType == ProductType.Template || offeringType == ProductType.Ebook)if (offeringType == ProductType.Template || offeringType == ProductType.Ebook)Derek explains that this pattern quickly spreads across a large system. The same if else logic appears in multiple services, causing inconsistencies and bugs when adding a new enum value. For example, what happens when you introduce a new product type like Video? You’ll have to remember to update every single block where this conditional expression exists.
Repetition Amplifies Complexity
In the following example, Derek drills into nested conditionals. Inside one method, an if else statement checks the same enum, and that result is passed to yet another method, which also contains a similar check.
The statement checks for Template or Ebook, and returns something—otherwise, it returns null. Derek notes that this redundancy doesn’t just make the code longer, it introduces maintenance hazards. The same logic is copied across multiple files, leading to control flow chaos.
If your system requires you to add a default case every time you touch an enum, you know something’s off.
Changing the Way We Think About Conditionals
Instead of constantly checking types with if else, Derek proposes asking a better question:
Does the product have downloadable characteristics?
This is a better expression of intent. It makes your code more readable and reduces reliance on the enum entirely. Rather than writing an if statement with two conditions like:
if (product.Type == ProductType.Template || product.Type == ProductType.Ebook)if (product.Type == ProductType.Template || product.Type == ProductType.Ebook)You can simply encapsulate that logic in a model and write:
if (product.HasDownloadableResource())if (product.HasDownloadableResource())This returns true only when a downloadable resource is present—reducing the need for complex conditional expressions.
From If Statements to Encapsulated Behavior
To solve the core issue, Derek introduces a DownloadableResource type. This type includes a download URL and a default filename. It becomes a first-class part of your domain, rather than relying on if statements to derive it.
Now, instead of repeating this:
if (product.Type == ProductType.Template)
{
// Generate file name
}
else if (product.Type == ProductType.Ebook)
{
// Generate file name
}if (product.Type == ProductType.Template)
{
// Generate file name
}
else if (product.Type == ProductType.Ebook)
{
// Generate file name
}You write this:
var downloadable = product.GetDownloadableResource();
if (downloadable != null)
{
Console.WriteLine(downloadable.FileName);
}var downloadable = product.GetDownloadableResource();
if (downloadable != null)
{
Console.WriteLine(downloadable.FileName);
}This simplifies logic dramatically and eliminates the need for else statement branches or even a switch statement.
Runtime Over Compile Time: A Strategic Shift
Derek takes it further by explaining a significant design choice: shifting logic from compile-time to runtime. This means querying the system at runtime to see if a DownloadableResource exists for a product. If it does, act on it. If not, skip it.
He notes that this move turns static if else logic into runtime queries. It might add a database call, but it reduces nested if else logic and centralizes behavior. This improves maintainability at scale.
Using Inheritance for Downloadable Products
Another route Derek explores is inheritance. You can create an abstract base class Product and then define derived types like Ebook, Template, or OfflineCourse.
Each one overrides methods like:
public virtual string GetDownloadUrl() { ... }public virtual string GetDownloadUrl() { ... }This approach allows each product to handle its own logic. While this avoids a switch statement or multiple conditional statements, Derek points out that you can still end up writing conditional expressions internally if you’re not careful.
Better Encapsulation without Inheritance
If inheritance feels heavy-handed, Derek suggests using explicit types like DownloadableProduct, which contains its own properties and methods—without being tied to a hierarchy.
In your program, this could look like:
var downloader = new DownloadableProduct(product);
Console.WriteLine(downloader.GetDefaultFileName());var downloader = new DownloadableProduct(product);
Console.WriteLine(downloader.GetDefaultFileName());There’s no need for an if else or switch statement to determine the behavior—each object knows what to do.
Lightweight Fix: Extension Methods on Enums
If you're not ready to abandon enums, Derek proposes a lightweight solution—create an extension method:
public static bool IsDownloadable(this ProductType type)
{
return type == ProductType.Template || type == ProductType.Ebook;
}public static bool IsDownloadable(this ProductType type)
{
return type == ProductType.Template || type == ProductType.Ebook;
}Now instead of writing:
if (product.Type == ProductType.Template || product.Type == ProductType.Ebook)if (product.Type == ProductType.Template || product.Type == ProductType.Ebook)You can simplify it to:
if (product.Type.IsDownloadable())if (product.Type.IsDownloadable())This centralizes your logic and avoids repeating the curly brackets and block of code again and again.
Avoid Overusing Ternary Operators and Switch
Derek also cautions against overusing shorthand like the ternary operator:
string filename = product.Type == ProductType.Template ? "template.pdf" : "default.pdf";string filename = product.Type == ProductType.Template ? "template.pdf" : "default.pdf";While it's valid syntax, it can be error-prone and hard to read when logic gets complex. Especially if your condition evaluates to false, the wrong value might be assigned in subtle ways.
Similarly, switch with a break statement and default case also falls into this trap. It's better to ask objects for behavior than use switch-case logic.
Conclusion: Smarter Control with Less Conditional Clutter
In conclusion, Derek's video is not an attack on enums—but a critique of how we use conditional if structures around them. By spreading if else and switch statements across your codebase, you make the system harder to test, maintain, and evolve.
Whether you opt for encapsulation, runtime lookup, inheritance, or simple extension methods, the goal remains the same: reduce conditionals, and move logic to where it belongs.
Remember:
Conditionals aren't bad.
Conditional clutter is.
Clean code doesn’t rely on multiple if else statements scattered across classes.
- Evaluate your context and refactor accordingly.
As Derek says, “It depends on your context.” But one thing is certain: a product isn’t always just a product—sometimes, it’s a signal to rethink your design.
