C# で RetryHandler ってやつを実装してみた

仕事では調査や資料作成が多くて、ここのところ C# でコード書いていない。たまには書かないと忘れちゃいそうだし、何かお題ないかな〜って探してたら、こんなのを発見。

業務アプリでもリトライを実装することはまぁまぁあるので、C# でも実装してみるよ。

namespace RetryHandlerSample
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading;

    public class RetryException : Exception
    {
        public RetryException(string message)
            : base(message)
        {
        }

        public RetryException(string message, Exception innerException)
            : base(message, innerException)
        {
        }
    }

    public static class RetryHandler
    {
        public static void Retry(int retryCount, Action action)
        {
            Retry<Exception>(retryCount, action);
        }

        public static void Retry<TException>(int retryCount, Action action)
            where TException : Exception
        {
            Retry<TException>(retryCount, action, 0);
        }

        public static void Retry(int retryCount, Action action, int sleepTime)
        {
            Retry<Exception>(retryCount, action, sleepTime);
        }

        public static void Retry<TException>(int retryCount, Action action, int sleepTime)
            where TException : Exception
        {
            Retry<int, TException>(retryCount, () =>
            {
                action();
                return -1;
            }, sleepTime);
        }

        public static TResult Retry<TResult>(int retryCount, Func<TResult> action)
        {
            return Retry<TResult, Exception>(retryCount, action);
        }

        public static TResult Retry<TResult>(int retryCount, Func<TResult> action, int sleepTime)
        {
            return Retry<TResult, Exception>(retryCount, action, sleepTime);
        }

        public static TResult Retry<TResult, TException>(int retryCount, Func<TResult> action)
            where TException : Exception
        {
            return Retry<TResult, TException>(retryCount, action, 0);
        }

        public static TResult Retry<TResult, TException>(int retryCount, Func<TResult> action, int sleepTime)
            where TException : Exception
        {
            if (retryCount < 0) throw new ArgumentOutOfRangeException("retryCount");
            if (action == null) throw new ArgumentNullException("action");
            if (sleepTime < 0) throw new ArgumentOutOfRangeException("sleepTime");

            for (var i = retryCount + 1; 0 < i; i--)
            {
                try
                {
                    return action();
                }
                catch (Exception ex)
                {
                    if ((0 < i) && (ex is TException))
                    {
                        if (0 < sleepTime)
                        {
                            Thread.Sleep(sleepTime);
                        }
                    }
                    else
                    {
                        throw new RetryException("リトライに失敗しました。", ex);
                    }
                }
            }
            throw new RetryException("リトライに失敗しました。");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // 3回失敗するメソッドの呼び出しを3回リトライする
            var result = RetryHandler.Retry(3, GenerateFaultMethod(3));
            Console.WriteLine(result);

            // 3回失敗するメソッドの呼び出しを2回リトライする
            try
            {
                var result2 = RetryHandler.Retry(2,
                    GenerateFaultMethod(3));
                Console.WriteLine(result);
            }
            catch (RetryException ex)
            {
                Console.WriteLine(ex.Message);
            }

            Console.ReadLine();
        }

        // 指定した回数だけ呼び出し失敗するメソッドを返す
        private static Func<string> GenerateFaultMethod(int count)
        {
            return () =>
            {
                if (0 < count)
                {
                    count--;
                    throw new Exception();
                }
                return "success";
            };
        }
    }
}

捕捉する例外の型をタイプセーフに指定したかったので、ジェネリックメソッドにしてみたけど、なんかしっくりこない。かといって、Type だと Exception を継承したクラス以外も渡せてしまうし。まぁ、妥協しておこうかな。

生粋の LINQ 星人なら Reactive Extensions でやってしまうのかもしれないけどね。