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
147: The Adapter Pattern
Imagine you've just landed in London for a business trip. You pull your laptop charger out of your bag, ready to power up, only to realize your plug has two flat pins (North American style) and the wall socket has three rectangular holes (UK style). Now, you have two choices: you could rip open the wall and rewire the building's electrical system, or you could spend five dollars on a plastic travel adapter.
In software, we face this exact scenario all the time. You have a modern interface that your entire application relies on, but you've been told you need to integrate a third-party library or a legacy system that does exactly what you need—it just does it using different method names, different parameters, or a completely different structure. You can't rewrite the third-party library (you don't even have the source code), and you don't want to rewrite your whole app to accommodate one weird library. So, you build an Adapter.
The 'Square Peg, Round Hole' Conflict
Let's look at a concrete example. Suppose your app uses a standard ILogger interface for all its logging needs. Every part of your system knows how to call Log(string message). It's clean, it's consistent, and it works.
public interface ILogger
{
void Log(string message);
}
Then, your boss tells you that you must use a specific, high-performance legacy logging library called UltraLog. The problem? UltraLog doesn't know what an ILogger is. Its method is called WriteLogEntry and it requires a priority level as an integer. It looks like this:
// This is the "Adaptee" - we cannot change this code
public class UltraLog
{
public void WriteLogEntry(string text, int priority)
{
Console.WriteLine($"[Priority {priority}] {text}");
}
}
If you try to pass UltraLog into a method that expects ILogger, the compiler will scream at you. They are fundamentally incompatible, even though they both technically "log things."
Building the Bridge
Instead of changing your entire codebase to use WriteLogEntry, we create an Adapter class. This class implements the interface your app expects (the "Target"), but internally, it holds a reference to the legacy class (the "Adaptee") and translates the calls.
public class UltraLogAdapter : ILogger
{
private readonly UltraLog _ultraLog;
public UltraLogAdapter(UltraLog ultraLog)
{
_ultraLog = ultraLog;
}
public void Log(string message)
{
// We "adapt" the call. Since ILogger doesn't provide a priority,
// we decide on a sensible default (like 1) for the legacy system.
_ultraLog.WriteLogEntry(message, 1);
}
}
Now, the rest of your application doesn't even know UltraLog exists. It just sees an ILogger. I've found that this is one of the most satisfying patterns to implement because it keeps your core business logic "pure"—it stays decoupled from the quirks of external vendors.
When to reach for this pattern
I'll be honest: it's easy to over-engineer things. You don't need an adapter for every single class. Use this when you're dealing with "The Outside World." If you're integrating a NuGet package, a legacy DLL, or a third-party API, wrap it in an adapter. If that vendor ever changes their API or you decide to swap them out for a competitor, you only have to change the code in one single adapter class rather than hunting through 50 different files to rename a method.
📋 Practical Task
Implement a Stripe-to-Internal Payment Adapter
You are working on an e-commerce platform. Your system uses an internal interface IPaymentProcessor to handle transactions. You need to integrate a third-party library called StripeApi, but the method signatures don't match.
The Requirements:
- The internal interface is
IPaymentProcessorwith a method:void ProcessPayment(decimal amount). - The third-party class
StripeApihas a method:void ChargeCustomer(double amountInCents, string currency). - Create a
StripeAdapterclass that implementsIPaymentProcessor. - Inside the adapter, convert the
decimal amount(which is in dollars) todouble amountInCents(amount * 100) and hardcode the currency as "USD".
Starter Code:
public interface IPaymentProcessor
{
void ProcessPayment(decimal amount);
}
public class StripeApi
{
public void ChargeCustomer(double amountInCents, string currency)
{
Console.WriteLine($"Charged {amountInCents} {currency} via Stripe.");
}
}
// TODO: Implement StripeAdapter here
public class Program
{
public static void Main()
{
// Your test code:
// 1. Instantiate StripeApi
// 2. Wrap it in StripeAdapter
// 3. Call ProcessPayment(19.99m) using the IPaymentProcessor interface
}
}There are no comments for now.