Delegates in C#: A Practical Approach to the Open/Closed Principle (OCP)

Shayan Kamalzadeh
3 min readFeb 18, 2025

--

Delegate in c#

Hello my friends, Today I want to talk about Delegates in c#

I think most developers are familiar with the concept of delegates, so I do not want to talk about how it is defined, I want to talk about why it can be helpful especially in real applications(because we see always a lot of samples that it shows that delegates are useful for transfer methods (so what?)

one of reasons that I use delegates is OCP(open-close principle) .let me show you a sample without delegates:

class PaymentProcessor
{
public void ProcessPayment(string type, double amount)
{
if (type == "CreditCard")
{
Console.WriteLine($"Processing credit card payment of ${amount}");
}
else if (type == "PayPal")
{
Console.WriteLine($"Processing PayPal payment of ${amount}");
}
else if (type == "BankTransfer")
{
Console.WriteLine($"Processing bank transfer of ${amount}");
}
else
{
Console.WriteLine("Invalid payment type");
}
}
}

class Program
{
static void Main()
{
PaymentProcessor processor = new PaymentProcessor();
processor.ProcessPayment("CreditCard", 100);
processor.ProcessPayment("PayPal", 200);
}
}

In this sample, we have a method that gets the type of payment and amount.

If you are familiar with SOLID you know it is against OCP. The first way (most of us use for refactoring it) is using an interface like this code:

using System;

interface IPaymentMethod
{
void Process(double amount);
}

class CreditCardPayment : IPaymentMethod
{
public void Process(double amount)
{
Console.WriteLine($"Processing credit card payment of ${amount}");
}
}

class PayPalPayment : IPaymentMethod
{
public void Process(double amount)
{
Console.WriteLine($"Processing PayPal payment of ${amount}");
}
}

class PaymentProcessor
{
public void ProcessPayment(IPaymentMethod paymentMethod, double amount)
{
paymentMethod.Process(amount);
}
}

class Program
{
static void Main()
{
PaymentProcessor processor = new PaymentProcessor();

processor.ProcessPayment(new CreditCardPayment(), 100);
processor.ProcessPayment(new PayPalPayment(), 200);
}
}

Advantages of Using Interfaces

  1. More structured and reusable — Each payment method is a concrete class that can have additional logic.
  2. Encapsulation — Each payment method handles its logic without exposing implementation details.
  3. Better for Dependency Injection (DI) — This can be easily used in loosely coupled architectures.
  4. More discoverable — Developers can easily find and extend payment methods if working in large teams.

Disadvantages of Interfaces

  1. More boilerplate code — Every new payment method needs a new class.
  2. Overkill for small cases — If you need to pass a simple function, creating a full class is unnecessary.
  3. Less flexibility — You must create an object instead of just passing a method.

Now let me implement this approach with delegate:

using System;

class PaymentProcessor
{
public void ProcessPayment(Action<double> paymentMethod, double amount)
{
paymentMethod(amount);
}
}

class Program
{
static void CreditCardPayment(double amount)
{
Console.WriteLine($"Processing credit card payment of ${amount}");
}

static void PayPalPayment(double amount)
{
Console.WriteLine($"Processing PayPal payment of ${amount}");
}

static void Main()
{
PaymentProcessor processor = new PaymentProcessor();

processor.ProcessPayment(CreditCardPayment, 100); d
processor.ProcessPayment(PayPalPayment, 200);
}
}

I used Action<>( It is a kind of delegate in c#).

How Delegates Solve the Problem

  1. Removes Hardcoded Logic: No need to modify ProcessPayment when adding a new payment type.
  2. Extensible & Maintainable: New payment methods can be added without modifying the existing class.
  3. Follows Open/Closed Principle: The method is open for extension but closed for modification.
  4. Flexible: The caller decides the method at runtime.

When Should You Use Delegates?

Use delegates when:

  1. You want to allow custom behaviors without modifying existing code.
  2. You are implementing event-driven programming (e.g., UI event handlers, and logging systems).
  3. You want to decouple components, making code more maintainable and testable.

In nutshell:

Using interface for big systems and using delegates for quick and dynamic behaviors. Using delegates in legacy code to achieve the Open/Closed Principle (OCP) is a great idea because it allows you to extend functionality without modifying existing code.

--

--

No responses yet