C# Action Delegates
Introduction
Action delegates are one of C#'s built-in delegate types that provide a convenient way to work with methods that don't return a value (void methods). They were introduced in C# 3.0 as part of the .NET Framework 3.5 and are widely used in modern C# programming, especially in LINQ, asynchronous programming, and event handling.
The Action delegate family allows you to reference methods with up to 16 parameters without having to define custom delegate types. This makes your code cleaner, more concise, and easier to maintain.
Understanding Action Delegates
An Action delegate represents a method that:
- Returns void (doesn't return a value)
- Can accept between 0 and 16 input parameters of various types
The basic syntax for Action delegates is:
// Action with no parameters
Action myAction;
// Action with parameters
Action<T1> myAction;
Action<T1, T2> myAction;
// ... up to
Action<T1, T2, T3, ..., T16> myAction;
Where T1, T2, etc. are the parameter types for the method the delegate will point to.
Using Action Delegates Without Parameters
Let's start with the simplest form - an Action delegate that points to a method with no parameters:
using System;
class Program
{
static void Main()
{
// Creating an Action delegate that references DisplayMessage method
Action showMessage = DisplayMessage;
// Invoking the delegate
showMessage();
// Using an anonymous method with Action
Action greet = delegate()
{
Console.WriteLine("Hello from anonymous method!");
};
greet();
// Using a lambda expression with Action
Action farewell = () => Console.WriteLine("Goodbye from lambda expression!");
farewell();
}
static void DisplayMessage()
{
Console.WriteLine("Hello from DisplayMessage method!");
}
}
Output:
Hello from DisplayMessage method!
Hello from anonymous method!
Goodbye from lambda expression!
As you can see, we've created Action delegates in three different ways:
- By directly referencing an existing method
- By using an anonymous method
- By using a lambda expression
Action Delegates with Parameters
Now, let's see how to use Action delegates with parameters:
using System;
class Program
{
static void Main()
{
// Action with one parameter
Action<string> printMessage = message => Console.WriteLine(message);
printMessage("This is a simple message");
// Action with two parameters
Action<string, int> repeatMessage = (message, times) =>
{
for (int i = 0; i < times; i++)
{
Console.WriteLine($"{i+1}. {message}");
}
};
repeatMessage("Hello, world!", 3);
// Action with multiple parameters of different types
Action<string, int, bool> processData = ProcessInformation;
processData("Test data", 42, true);
}
static void ProcessInformation(string data, int value, bool flag)
{
Console.WriteLine($"Processing: {data}, Value: {value}, Flag: {flag}");
}
}
Output:
This is a simple message
1. Hello, world!
2. Hello, world!
3. Hello, world!
Processing: Test data, Value: 42, Flag: True
In this example, we've created Action delegates that take one, two, and three parameters of different types. The Action<T>T
delegate requires you to specify the parameter types in the angle brackets.
Practical Uses of Action Delegates
1. Callbacks
Action delegates are perfect for implementing callback mechanisms:
using System;
using System.Threading;
class Program
{
static void Main()
{
Console.WriteLine("Starting a task...");
// Passing a callback function to be executed after task completion
PerformLongRunningTask(() =>
{
Console.WriteLine("Task completed!");
Console.WriteLine("Updating UI and performing cleanup...");
});
Console.WriteLine("Main thread continues execution...");
}
static void PerformLongRunningTask(Action onComplete)
{
// Simulate a long-running task
Thread.Sleep(2000);
Console.WriteLine("Long-running operation finished");
// Execute the callback when task is done
onComplete();
}
}
Output:
Starting a task...
Main thread continues execution...
Long-running operation finished
Task completed!
Updating UI and performing cleanup...
2. Event Handling
Action delegates are frequently used in event handling scenarios:
using System;
using System.Collections.Generic;
public class NotificationService
{
// List of subscribers (Action delegates)
private List<Action<string>> _subscribers = new List<Action<string>>();
// Method to add subscribers
public void Subscribe(Action<string> subscriber)
{
_subscribers.Add(subscriber);
}
// Method to send notifications to all subscribers
public void NotifyAll(string message)
{
foreach (var subscriber in _subscribers)
{
subscriber(message);
}
}
}
class Program
{
static void Main()
{
var notifier = new NotificationService();
// Adding subscribers
notifier.Subscribe(msg => Console.WriteLine($"Email service: {msg}"));
notifier.Subscribe(msg => Console.WriteLine($"SMS service: {msg}"));
notifier.Subscribe(msg => Console.WriteLine($"Push notification: {msg}"));
// Trigger notification
notifier.NotifyAll("System will be down for maintenance at 10 PM");
}
}
Output:
Email service: System will be down for maintenance at 10 PM
SMS service: System will be down for maintenance at 10 PM
Push notification: System will be down for maintenance at 10 PM
3. Collection Processing
Action delegates are often used when processing collections:
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
List<string> fruits = new List<string>
{
"Apple", "Banana", "Cherry", "Date", "Elderberry"
};
// Using ForEach with Action delegate
Console.WriteLine("Fruits in the list:");
fruits.ForEach(fruit => Console.WriteLine($"- {fruit}"));
// Using ForEach with a more complex Action
Console.WriteLine("\nFruit analysis:");
fruits.ForEach(fruit =>
{
Console.WriteLine($"Analyzing {fruit}:");
Console.WriteLine($" Length: {fruit.Length} characters");
Console.WriteLine($" First letter: {fruit[0]}");
Console.WriteLine($" Contains 'e': {fruit.Contains('e')}");
});
}
}
Output:
Fruits in the list:
- Apple
- Banana
- Cherry
- Date
- Elderberry
Fruit analysis:
Analyzing Apple:
Length: 5 characters
First letter: A
Contains 'e': True
Analyzing Banana:
Length: 6 characters
First letter: B
Contains 'e': False
...
Action vs Custom Delegates
Before Action delegates were introduced, you would need to define your own delegate types:
// Before Action delegates:
public delegate void PrintDelegate(string message);
// With Action delegates:
// Action<string> printDelegate; // No custom delegate definition needed
Using built-in Action delegates has several advantages:
- Reduces code verbosity (no need to define custom delegate types)
- Standardizes delegate usage across your codebase
- Makes code more readable for developers familiar with .NET conventions
- Makes it easier to use with generic methods and lambda expressions
Summary
Action delegates are a powerful feature in C# that allow you to:
- Reference methods that don't return values (void methods)
- Pass methods as parameters to other methods
- Create callback mechanisms
- Work with events and collections efficiently
- Create more concise code by avoiding custom delegate definitions
Action delegates are part of the .NET Framework's generic delegate types, alongside other delegate types like Func<>
(for methods that return values) and Predicate<>
(for methods that return a boolean).
Practice Exercises
-
Create a simple console application that uses an Action delegate to print different types of messages with varying formatting.
-
Implement a timer class that executes an Action delegate after a specified delay.
-
Create a simple event system using Action delegates where multiple subscribers can be notified when an event occurs.
-
Build a data processing pipeline that applies multiple transformations (represented by Action delegates) to a collection of data.
Additional Resources
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)