C#
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C#
-
Section 4: Working with Data
-
Section 5: Error Handling
-
Section 6: Delegates and Events
-
Section 7: Async Programming
-
Section 8: More Language Features
-
Section 9: File I/O and Serialization
-
Section 10: Networking in .NET
-
Section 11: The .NET Ecosystem
-
Section 12: Memory and Performance
-
Section 13: Concurrency Beyond Async
-
Section 14: Reflection and Attributes
-
Section 15: Testing and Best Practices
-
Section 16: Design Patterns in C#
-
Section 17: Standard Library Deep Dive
-
Section 18: Data Structures and Algorithms in C#
-
Section 19: GUI and Desktop Development Overview
-
Section 20: Practical Projects
-
Section 21: More Practice Exercises
-
Section 22: More Standard Library and Text Processing
-
Section 23: More Design Patterns
-
Section 24: More Projects
-
Section 25: Interview Practice
-
Section 26: C# Keywords Reference (Modifiers)
-
Section 27: C# Keywords Reference (Statements)
-
Section 28: C# Keywords Reference (Operators)
-
Section 29: BCL: System.Collections.Generic
-
Section 30: BCL: System.Linq
-
Section 31: BCL: System.Threading
-
Section 32: BCL: System.IO
-
Section 33: BCL: System.Text and System.Text.Json
-
Section 34: BCL: System.Net.Http
-
Section 35: C# Language Specification Topics
-
Section 36: More Practice Exercises
-
Section 37: More Async Patterns
-
Section 38: More BCL: System.Reflection and System.Diagnostics
-
Section 39: Nullable Reference Types In Depth
-
Section 40: C# Records and Pattern Matching In Depth
-
Section 41: Dependency Injection Deep Dive
-
Section 42: More Interview and Whiteboard Practice
149: The Chain of Responsibility Pattern
I see this all the time when developers first encounter the Chain of Responsibility (CoR) pattern: they look at the diagram, look at the code, and tell me, "Why would I do this? I can just use a switch statement or a bunch of if/else blocks in one method."
"It's just a fancy if-else block"
If you're thinking that, you're not entirely wrong about the result, but you're missing the architecture. Let's look at a quick example. Imagine you're building a support ticket system where a ticket goes from Level 1 support, to Level 2, and finally to a Manager.
// The "if-else" approach
public void ProcessTicket(Ticket ticket)
{
if (ticket.IsSimple) {
HandleLevel1(ticket);
} else if (ticket.IsTechnical) {
HandleLevel2(ticket);
} else {
HandleManager(ticket);
}
}
On the surface, this is clean. But here's the problem: the ProcessTicket method is a "God Method." It has to know about every single level of support. Every time the company adds a new tier (like "Specialist" or "Legal"), you have to open this file, modify this method, and risk breaking the entire routing logic. It violates the Open/Closed Principle—the idea that your code should be open for extension but closed for modification.
Decoupling the Decision from the Execution
The Chain of Responsibility flips this logic. Instead of one central brain deciding where a request goes, the request is passed along a chain of independent objects. Each object decides: "Can I handle this? If so, I'll do it. If not, I'll pass it to the next person in line."
The beauty here is that the sender of the request doesn't know who will eventually handle it, and the handlers don't need to know the full structure of the chain—only who their immediate successor is.
Here is how we actually implement this in C#:
public class SupportTicket
{
public string Description { get; set; }
public int Severity { get; set; } // 1: Low, 2: Medium, 3: High
}
public interface ISupportHandler
{
ISupportHandler SetNext(ISupportHandler handler);
void Handle(SupportTicket ticket);
}
public abstract class BaseHandler : ISupportHandler
{
private ISupportHandler _nextHandler;
public ISupportHandler SetNext(ISupportHandler handler)
{
_nextHandler = handler;
return handler; // Returning the handler allows for chaining
}
public virtual void Handle(SupportTicket ticket)
{
_nextHandler?.Handle(ticket);
}
}
public class Level1Handler : BaseHandler
{
public override void Handle(SupportTicket ticket)
{
if (ticket.Severity == 1) {
Console.WriteLine("Level 1: Handled the basic issue.");
} else {
Console.WriteLine("Level 1: Too complex. Passing it up...");
base.Handle(ticket);
}
}
}
public class Level2Handler : BaseHandler
{
public override void Handle(SupportTicket ticket)
{
if (ticket.Severity == 2) {
Console.WriteLine("Level 2: Handled the technical issue.");
} else {
Console.WriteLine("Level 2: Still too complex. Escalating to Manager...");
base.Handle(ticket);
}
}
}
public class ManagerHandler : BaseHandler
{
public override void Handle(SupportTicket ticket)
{
Console.WriteLine("Manager: I'll take care of this personally.");
}
}
Now, look at how we assemble and use this chain in our main program:
var level1 = new Level1Handler();
var level2 = new Level2Handler();
var manager = new ManagerHandler();
// Build the chain: Level 1 -> Level 2 -> Manager
level1.SetNext(level2).SetNext(manager);
var myTicket = new SupportTicket { Description = "Server is on fire", Severity = 3 };
level1.Handle(myTicket);
I want you to notice that level1 doesn't know the ManagerHandler even exists. It only knows about level2. If we want to add a LegalHandler between Level 2 and the Manager, we just change the setup code. We don't touch the logic inside Level1Handler or Level2Handler.
One quick word of caution: don't over-engineer. If you only have two possible paths and they will never change, a simple if statement is fine. Use CoR when the handling logic is complex, changes frequently, or when you want to dynamically reorder the sequence of handlers at runtime.
📋 Practical Task
Build a Multi-Stage Order Validation Pipeline
You are building an e-commerce checkout system. An order must pass through several validation checks before it can be processed. If any check fails, the chain should stop and report the error. If it passes, it moves to the next check.
Requirements:
- Create an
Orderclass with properties:decimal TotalAmount,bool IsGuestCheckout, andList<string> Items. - Implement a Chain of Responsibility to handle these three checks:
- EmptyOrderHandler: Fails if the
Itemslist is empty. - GuestLimitHandler: Fails if
IsGuestCheckoutis true ANDTotalAmountis over $100. - FraudCheckHandler: Fails if the
TotalAmountis over $10,000 (flagged for manual review).
- EmptyOrderHandler: Fails if the
- Ensure that if a handler fails, it prints "Validation Failed: [Reason]" and does not call the next handler in the chain.
- If all handlers pass, the final handler (or the end of the chain) should print "Order validated successfully!".
Test Case: Create a guest order for $150 and verify that the GuestLimitHandler stops the chain before it ever reaches the FraudCheckHandler.
There are no comments for now.