2 minute read

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