Skip to content

Instantly share code, notes, and snippets.

@musoftware
Created October 3, 2025 10:12
Show Gist options
  • Select an option

  • Save musoftware/dc5092a81fda4092f6119a9b2345b346 to your computer and use it in GitHub Desktop.

Select an option

Save musoftware/dc5092a81fda4092f6119a9b2345b346 to your computer and use it in GitHub Desktop.
Generic Retry Handler Class in C#
using System;
public class Program
{
private static readonly Random _opRandom = new Random();
public static void Main(string[] args)
{
Console.WriteLine("--- Running Action (void method) Example ---");
var serviceRetryPolicy = new RetryHandler(retries: 4, baseDelaySeconds: 0.5);
try
{
serviceRetryPolicy.Execute(ConnectToFlakyService);
}
catch (Exception e)
{
Console.WriteLine($"[FATAL] Could not connect to service: {e.GetType().Name}");
}
Console.WriteLine("\n--- Running Func<T> (method with return value) Example ---");
var apiRetryPolicy = new RetryHandler(retries: 5, baseDelaySeconds: 1.0);
try
{
string result = apiRetryPolicy.Execute(FetchDataFromUnstableApi);
Console.WriteLine($"[SUCCESS] Final result: {result}");
}
catch (Exception e)
{
Console.WriteLine($"[FATAL] Could not fetch data: {e.GetType().Name}");
}
}
public static void ConnectToFlakyService()
{
Console.WriteLine("Attempting to connect to service...");
if (_opRandom.NextDouble() < 0.75) // 75% chance of failure
{
throw new InvalidOperationException("Service is temporarily unavailable.");
}
Console.WriteLine("Service connection successful!");
}
public static string FetchDataFromUnstableApi()
{
Console.WriteLine("Attempting to fetch data from API...");
if (_opRandom.NextDouble() < 0.80) // 80% chance of failure
{
throw new TimeoutException("API call timed out.");
}
Console.WriteLine("Data fetched successfully!");
return "{\"data\": \"some important info\"}";
}
}
using System;
using System.Threading;
public class RetryHandler
{
private readonly int _retries;
private readonly double _baseDelaySeconds;
private readonly double _backoffFactor;
private readonly double _jitter;
private static readonly Random _random = new Random();
public RetryHandler(int retries = 3, double baseDelaySeconds = 1.0, double backoffFactor = 2.0, double jitter = 0.1)
{
_retries = retries;
_baseDelaySeconds = baseDelaySeconds;
_backoffFactor = backoffFactor;
_jitter = jitter;
}
// Overload for methods that don't return a value (void)
public void Execute(Action action)
{
double currentDelay = _baseDelaySeconds;
for (int attempt = 0; attempt < _retries; attempt++)
{
try
{
action();
return; // Success
}
catch (Exception e)
{
if (attempt == _retries - 1)
{
Console.WriteLine($"[ERROR] Action failed after {_retries} retries.");
throw; // Re-throw the last exception
}
double sleepTime = currentDelay + _random.NextDouble() * _jitter;
Console.WriteLine($"[WARN] Attempt {attempt + 1}/{_retries} failed: {e.Message}. Retrying in {sleepTime:F2} seconds...");
// Thread.Sleep expects milliseconds
Thread.Sleep((int)(sleepTime * 1000));
currentDelay *= _backoffFactor;
}
}
}
// Generic overload for methods that return a value
public T Execute<T>(Func<T> function)
{
double currentDelay = _baseDelaySeconds;
for (int attempt = 0; attempt < _retries; attempt++)
{
try
{
return function(); // Success
}
catch (Exception e)
{
if (attempt == _retries - 1)
{
Console.WriteLine($"[ERROR] Function failed after {_retries} retries.");
throw;
}
double sleepTime = currentDelay + _random.NextDouble() * _jitter;
Console.WriteLine($"[WARN] Attempt {attempt + 1}/{_retries} failed: {e.Message}. Retrying in {sleepTime:F2} seconds...");
Thread.Sleep((int)(sleepTime * 1000));
currentDelay *= _backoffFactor;
}
}
// This line is technically unreachable but required by the compiler
throw new InvalidOperationException("Retry logic failed unexpectedly.");
}
}
@musoftware
Copy link
Author

musoftware commented Oct 3, 2025

Centralized Logic: This pattern centralizes all the complex retry logic into one class. Instead of scattering try-catch loops throughout your code, you instantiate and use this handler, making your code much cleaner.
Strongly-Typed: By using Action for void methods and Func for methods with return types, you get compile-time safety and clarity about what kind of method you're executing.
Policy-Based Design: You can create different instances of RetryHandler with different configurations (e.g., a "fast retry" policy for internal services, a "slow retry" policy for external APIs) and apply them as needed.
Framework Independent: This is a plain C# solution that doesn't require any external libraries like Polly (though for very complex scenarios, Polly is an excellent choice). It's a great lightweight tool to have in your arsenal.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment