DEV Community

Cover image for Mastering Action and Func Delegates in C#: Real-World Patterns and Examples
Spyros Ponaris
Spyros Ponaris

Posted on • Edited on

Mastering Action and Func Delegates in C#: Real-World Patterns and Examples

🔧 Mastering Action and Func Delegates in C#: Real-World Patterns and Examples
In C#, delegates like Action and Func allow you to treat behavior as data — meaning you can pass methods around like variables.

This concept is powerful and opens doors to cleaner, more flexible, and decoupled code.

🧠 In This Article:

What Action and Func are

How to use them effectively

Real-world examples using design patterns like Strategy, Command, and Response pipelines

🔹 What Are Action and Func in C#?

✅ Action Delegate
Represents a method that returns void and can take 0 to 16 parameters.

Action<string> greet = name => Console.WriteLine($"Hello, {name}!");
greet("Alice"); // Output: Hello, Alice!
Enter fullscreen mode Exit fullscreen mode

✅ Func Delegate

Represents a method that returns a value, with 0 to 16 parameters (last one is the return type).

Func<int, int, int> add = (a, b) => a + b;
int sum = add(3, 4); // sum = 7
Enter fullscreen mode Exit fullscreen mode

💡 Why Use Action and Func?

Decouple logic from implementation

Enable cleaner LINQ, event handling, callbacks, and dynamic behavior

Help implement design patterns with less boilerplate

🎯 Real-World Example 1: Strategy Pattern Using Func

The Strategy Pattern allows you to swap algorithms at runtime. Here’s how it looks using Func:

public class TaxCalculator
{
    private readonly Func<decimal, decimal> _taxStrategy;

    public TaxCalculator(Func<decimal, decimal> taxStrategy)
    {
        _taxStrategy = taxStrategy;
    }

    public decimal Calculate(decimal amount) => _taxStrategy(amount);
}
Enter fullscreen mode Exit fullscreen mode

✅ Usage:

var standardTax = new TaxCalculator(amount => amount * 0.2m);
Console.WriteLine(standardTax.Calculate(100)); // Output: 20

var reducedTax = new TaxCalculator(amount => amount * 0.1m);
Console.WriteLine(reducedTax.Calculate(100)); // Output: 10
Enter fullscreen mode Exit fullscreen mode

✔ Clean and flexible, no need for extra strategy classes.

🎮 Real-World Example 2: Command Pattern Using Action
Encapsulate requests using Action, like a simple remote control:

public class RemoteControl
{
    private readonly Action _command;

    public RemoteControl(Action command)
    {
        _command = command;
    }

    public void PressButton() => _command();
}
Enter fullscreen mode Exit fullscreen mode

✅ Usage:

var turnOnLight = new RemoteControl(() => Console.WriteLine("Light On"));
turnOnLight.PressButton(); // Output: Light On

var playMusic = new RemoteControl(() => Console.WriteLine("Music Playing"));
playMusic.PressButton(); // Output: Music Playing
Enter fullscreen mode Exit fullscreen mode

🔁 Combining Action and Func: Workflow Example
Simulate a simple data pipeline using both Func and Action:

Func<string> fetchData = () => "raw data";
Func<string, string> processData = data => data.ToUpper();
Action<string> saveData = result => Console.WriteLine($"Saved: {result}");

var raw = fetchData();
var processed = processData(raw);
saveData(processed); // Output: Saved: RAW DATA
Enter fullscreen mode Exit fullscreen mode

📌 Chaining Operations with Func and Extension Methods
You can build your own functional pipelines using extension methods:

public static class Extensions
{
    public static T PerformOperation<T>(this T value, Func<T, T> performer) where T : struct
    {
        return performer(value);
    }
}
Enter fullscreen mode Exit fullscreen mode

✅ Usage:

public void DoWorkWithStandardLambda()
{
    Func<int, int> toPowerFour = x => x * x * x * x;
    Func<int, int> makeNegative = x => -1 * x;

    int value = 5;
    int resultA = value
        .PerformOperation(toPowerFour)
        .PerformOperation(makeNegative)
        .PerformOperation(x => x + 10); // Final result: -615

    double doubleVal = 7.5;
    double resultB = doubleVal
        .PerformOperation(x => Math.Sin(x))
        .PerformOperation(x => x * Math.PI);
}

Enter fullscreen mode Exit fullscreen mode

🔄 Functional Pipelines with a Response Pattern
You can adapt PerformOperation to work with a Result wrapper for success/failure chaining:

🧱 Result Class

public class Result<T>
{
    public bool IsSuccess { get; }
    public T Value { get; }
    public string? Error { get; }

    public Result(T value) => (IsSuccess, Value) = (true, value);
    public Result(string error) => (IsSuccess, Error) = (false, error);
}
Enter fullscreen mode Exit fullscreen mode

🔧 Chained Perform for Result:

public static class ResultExtensions
{
    public static Result<T> Perform<T>(this Result<T> result, Func<T, T> func)
    {
        return result.IsSuccess
            ? new Result<T>(func(result.Value))
            : result;
    }
}
Enter fullscreen mode Exit fullscreen mode

✅ Usage:

var result = new Result<int>(5)
    .Perform(x => x * x)
    .Perform(x => x + 10); // Final value: 35
If there's an error, the chain short-circuits:

var errorResult = new Result<int>("Invalid input")
    .Perform(x => x * x); // Skipped
Enter fullscreen mode Exit fullscreen mode

🧠 Final Thoughts

Understanding and leveraging Action and Func can radically simplify your C# code. From dynamic strategies to functional pipelines, they help you express logic clearly and flexibly — without drowning in boilerplate.

Before you create another class just to pass a behavior, ask yourself:

“Can I just use a delegate instead?”

References

Official Microsoft Docs - Delegates

C# Action Delegate

C# Func Delegate

GitHub Repo: MakeUseOfActionAndFuncDelegate

Top comments (2)

Collapse
 
chami profile image
Mohammed Chami • Edited

hey, thanks for sharing, you can also use CSharp syntax highlighting this way:

    int value = 10;
Enter fullscreen mode Exit fullscreen mode

Image description

Collapse
 
stevsharp profile image
Spyros Ponaris

Great, thanks! Unfortunately, I'm not very good at formatting.
I’ll keep it in mind for next time.