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
Interview Prep
Practice concise answers, then expand each card for the explanation.
1BeginnerQuestionDelegate vs interface?+
Answer
2BeginnerQuestionFunc vs Action?+
Answer
3IntermediateQuestionMulticast delegate?+
Answer
4IntermediateQuestionCovariance on Func?+
Answer
5AdvancedQuestionImplement retry with exponential backoff via delegate.+
Answer
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.