Retry Logic in C#: Why Passing a Function (Factory Delegate) Matters
When building integrations with REST services or remote systems, we frequently encounter intermittent failures—null responses, incomplete data, or unexpected exceptions. A single failed call is often not the real issue; the challenge arises when the retry mechanism does not actually re-execute the operation. This happens when we pass the result of a method rather than the method itself.
A reliable pattern to address this problem is to use a function (factory delegate). This ensures that each retry performs a fresh attempt, giving the operation a chance to succeed if the failure was temporary.
🧩 The Issue with Passing Pre-Evaluated Values
Consider the following pattern:
var feed = GetAllVersions(...);
var result = RetryUtils.WithRetry(feed);
In this scenario, the retry routine receives a final value. If the first fetch returned null, the retry mechanism simply handles the same null on every attempt. No fresh calls are made, and the retry becomes ineffective.
🛠️ The Factory Delegate Solution
A better approach is to pass a function that contains the logic:
var result = RetryUtils.WithRetry(() => GetAllVersions(...));
This changes the behavior significantly. Each retry now calls the delegate, creating a new opportunity for success. Transient issues such as service delays or network interruptions can be naturally recovered.
🔄 How the Delegate Enables Real Retries
A factory delegate stores the operation without executing it:
Func<T> fetchOperation = () => PerformOperation();
This delegate is invoked inside the retry loop. As the retry count increases, each iteration triggers another execution, ensuring we are not reusing any failed result.
🧪 Simple Example of Retry Logic Using a Factory Delegate
public static T Retry<T>(Func<T> action, int maxRetries = 3)
{
T lastResult = default;
for (int i = 0; i <= maxRetries; i++)
{
try
{
lastResult = action();
if (lastResult != null)
return lastResult;
}
catch
{
// errors are tolerated within retry attempts
}
Thread.Sleep(1000);
}
return lastResult;
}
Example usage:
var feed = Retry(() => d.GetAllVersions<Document>(FeedGetOptionsFactory.Full.Value));
Each retry performs a new request, improving resilience during temporary failures.
📘 When This Pattern Helps
This pattern becomes highly effective when dealing with:
- cloud platforms prone to intermittent latency,
- APIs that occasionally return null or incomplete data,
- operations that must be attempted multiple times before succeeding,
- integrations with content management systems like Documentum or SharePoint.
By ensuring each retry repeats the operation, we reduce the risk of failure caused by unstable external services.
🏁 Conclusion
Passing a function rather than a pre-evaluated result is a small shift with a significant impact. Factory delegates make retry logic reliable by ensuring every attempt performs genuine work. In C#, this approach strengthens the resilience of our integrations and improves overall system stability.
This technique is a valuable addition to our development toolkit, especially when building robust applications that interact with unpredictable external systems.
Leave a comment