3
\$\begingroup\$

I have a method which should accept a Function/Action and do something with that method (in my case I've added retry logic).

I'm wondering how I can remove the duplicated code (for loop, etc) from the last 2 methods. Is there any clever way to do this in C#?

public static class Retry {
    public static void Do(
    Type retryOnExceptionType,
    Action action,
    TimeSpan retryInterval =
    default (TimeSpan),
    int retryCount = 3) {
        if (retryInterval ==
        default (TimeSpan)) retryInterval = TimeSpan.FromSeconds(1);

        Do < object > (
        retryOnExceptionType, () = > {
            action();
            return null;
        }, retryInterval, retryCount);
    }

    public static T1 Do < T1, T2, T3 > (
    Type retryOnExceptionType,
    Func < T2, T3, T1 > action,
    T2 param1,
    T3 param2,
    TimeSpan retryInterval =
    default (TimeSpan),
    int retryCount = 3) {
        if (retryInterval ==
        default (TimeSpan)) retryInterval = TimeSpan.FromSeconds(1);

        var exceptions = new List < Exception > ();

        for (int retry = 0; retry < retryCount; retry++) {
            try {
                return action(param1, param2);
            } catch (Exception ex) {
                if (!retryOnExceptionType.IsInstanceOfType(ex)) throw;

                exceptions.Add(ex);
                Thread.Sleep(retryInterval);
            }
        }

        throw new AggregateException(exceptions);
    }


    public static T Do < T > (
    Type retryOnExceptionType,
    Func < T > action,
    TimeSpan retryInterval =
    default (TimeSpan),
    int retryCount = 3) {
        if (retryInterval ==
        default (TimeSpan)) retryInterval = TimeSpan.FromSeconds(1);

        var exceptions = new List < Exception > ();

        for (int retry = 0; retry < retryCount; retry++) {
            try {
                return action();
            } catch (Exception ex) {
                if (!retryOnExceptionType.IsInstanceOfType(ex)) throw;

                exceptions.Add(ex);
                Thread.Sleep(retryInterval);
            }
        }

        throw new AggregateException(exceptions);
    }
}
\$\endgroup\$
1
  • 4
    \$\begingroup\$ Your code indentaton is horribly non-standard and hard to read. :( \$\endgroup\$
    – ANeves
    Commented Oct 24, 2014 at 9:29

1 Answer 1

6
\$\begingroup\$

You should be able to wrap up your loops in a single method that takes a Func<T> and then do something like this:

public static T1 Do<T1,T2,T3>(Type retryOnExceptionType, Func<T2,T3,T1> action, T2 param1,
                              T3 param2, TimeSpan retryInterval, int retryCount)
{
    var del = new Func<T1>(() => action(param1, param2));
    return DoLoops<T1>(del, retryOnExceptionType, retryInterval, retryCount);
}


public static T Do<T>(Type retryOnExceptionType, Func<T> action,TimeSpan retryInterval,
                      int retryCount)
{
    return DoLoops<T>(action, retryOnExceptionType, retryInterval, retryCount);
}

private static T DoLoops<T>(Func<T> func, Type retryOnExceptionType,
                            TimeSpan retryInterval, int retryCount)
{
    if (retryInterval == default(TimeSpan))
        retryInterval = TimeSpan.FromSeconds(1);

    var exceptions = new List<Exception>();

    for (int retry = 0; retry < retryCount; retry++)
    {
        try
        {
            return func();
        }
        catch (Exception ex)
        {
            if (!retryOnExceptionType.IsInstanceOfType(ex))
                throw;

            exceptions.Add(ex);
            Thread.Sleep(retryInterval);
        }
    }

    throw new AggregateException(exceptions);
}

The key is in wrapping up your Func<T2, T3, T1> call in a separate Func<T1>, which allows you to pass a parameterless delegate to DoLoops regardless of how many type parameters the original delegate has, since that's the only difference between the original methods.

Even simpler, you can do away with DoLoops and just keep the for loop in the public static T Do<T> overload and Do<T1, T2, T3> can just pass the parameterless delegate it creates into that method:

public static T1 Do<T1,T2,T3>(Type retryOnExceptionType, Func<T2,T3,T1> action, T2 param1,
                              T3 param2, TimeSpan retryInterval, int retryCount)
{
    return Do<T1>(retryOnExceptionType, () => action(param1, param2),
                  retryInterval, retryCount);
}


public static T Do<T>(Type retryOnExceptionType, Func<T> action, TimeSpan retryInterval,
                      int retryCount)
{
    if (retryInterval == default(TimeSpan))
        retryInterval = TimeSpan.FromSeconds(1);

    var exceptions = new List<Exception>();

    for (int retry = 0; retry < retryCount; retry++)
    {
        try
        {
            return action();
        }
        catch (Exception ex)
        {
            if (!retryOnExceptionType.IsInstanceOfType(ex))
                throw;

            exceptions.Add(ex);
            Thread.Sleep(retryInterval);
        }
    }

    throw new AggregateException(exceptions);
}
\$\endgroup\$
1
  • \$\begingroup\$ Yea this is amazing! \$\endgroup\$
    – Piotr Kula
    Commented Oct 24, 2014 at 17:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.