Delegates
A delegate is a type-safe function pointer — references methods matching a signature.
Introduction
A delegate is a type-safe function pointer — references methods matching a signature. Action, Func, and Predicate built-ins cover common cases. Delegates enable callbacks, LINQ, and event foundations.
Multicast delegates combine multiple handlers with +=. Interviewers ask delegate vs interface callback and memory leaks from long-lived delegate references.
The story
An integration service calls an external credit bureau API that occasionally times out. Instead of duplicating retry loops everywhere, a RetryExecutor accepts any async operation as a Func<Task<T>> delegate and retries it three times with a delay — the same utility works for HTTP calls, database queries, or file uploads.
Understanding the topic
Key concepts
- delegate keyword declares method signature type.
- Action no return; Func
last type param return; Predicate bool. - Multicast += and -= combine invocations.
- Null delegate invoke throws NullReferenceException.
- Method group conversion: DoWork without parentheses.
- Func used extensively in LINQ lambdas.
Step-by-step explanation
- Declare delegate type or use built-in Action/Func.
- Assign method or lambda matching signature.
- Invoke with delegate(args) or delegate.Invoke(args).
- Multicast calls all subscribers in order.
- Covariance/contravariance on delegate parameters.
- Pass delegate to strategy methods.
Practical code example
Retry executor using Func
namespace TechLearningPro.Delegates;public static class RetryExecutor{public static async Task<T> ExecuteAsync<T>(Func<Task<T>> operation,int maxAttempts,TimeSpan delay){for (var attempt = 1; attempt <= maxAttempts; attempt++){try{return await operation();}catch when (attempt < maxAttempts){await Task.Delay(delay);}}throw new InvalidOperationException("Unreachable");}}// Usage: await RetryExecutor.ExecuteAsync(() => http.GetStringAsync(url), 3, TimeSpan.FromSeconds(1));
Line-by-line code explanation
ExecuteAsync<T>(Func<Task<T>> operation, ...)accepts any async lambda matching the delegate signature.for (var attempt = 1; attempt <= maxAttempts; attempt++)loops through retry attempts.try { return await operation(); }invokes the caller-supplied delegate and returns on success.catch when (attempt < maxAttempts)catches failures only when retries remain.await Task.Delay(delay)waits before the next attempt — backoff can be added here.throw new InvalidOperationException("Unreachable")satisfies the compiler after the loop — should never run.Func<Task<T>>is the type-safe function pointer that makes the executor reusable.() => http.GetStringAsync(url)is an example lambda passed as the operation delegate.
Key takeaway: Func
Real-world use
Where you'll use this in production
- LINQ Where/Select predicate parameters.
- Timer callbacks and ThreadPool.QueueUserWorkItem.
- Plugin hooks passing Action on lifecycle.
- Comparison delegates for Sort.
Best practices
- Prefer Func/Action over custom delegate types unless public API.
- Null-check before invoke or use ?.Invoke.
- Unsubscribe multicast to prevent leaks.
- Use interface when multiple related methods needed.
- Document thread context expectations on invoke.
Common mistakes
- Invoking null delegate.
- Multicast exception stops remaining handlers.
- Capturing large object in long-lived delegate.
- Confusing delegate type with invoked method return.
Advanced interview questions
Q1BeginnerDelegate vs interface?
Q2BeginnerFunc vs Action?
Q3IntermediateMulticast delegate?
Q4IntermediateCovariance on Func?
Q5AdvancedImplement retry with exponential backoff via delegate.
Summary
Delegates reference methods with type-safe signatures. Action, Func, Predicate cover common scenarios. Foundation for events and LINQ lambdas. Multicast enables multiple subscribers. Next: events on top of delegates.