C#でリトライ制御を共通化したい



# 1. はじめに
* DBの関連処理などリトライするケースが発生したので、処理を共通化したい
* メソッドの戻り値の有無があるので、どちらにも対応したい
* リトライ回数、リトライ間隔を設定できるようにしたい

# 2. 開発環境
* C#
* .Net 6
* Visual Studio 2022
* Windows 11

# 3. ソースコード
*Staticなユーティリティクラスを準備する

```C#:RetryExecteUtil.cs
/// <summary>
/// リトライ処理を行うユーティリティクラス
/// </summary>
public class RetryExecteUtil
{
    /// <summary>
    /// 処理を実行する
    /// 例外が発生した場合は指定秒数間隔でリトライする
    /// </summary>
    /// <typeparam name="T">リトライ対象メソッドの戻り値(型)</typeparam>
    /// <param name="retryMaxCount">最大リトライ回数</param>
    /// <param name="retryInterval">リトライ間隔(ミリ秒)</param>
    /// <param name="action">リトライ対象メソッド(戻り値あり)</param>
    /// <returns>処理結果</returns>
    public static T Execute<T>(int retryMaxCount, int retryInterval, Func<T> action)
    {
        // パラメータチェック
        if (retryMaxCount < 0)
        {
            // 最大リトライ回数
            throw new ArgumentOutOfRangeException("retryMaxCount");
        }
        if (retryInterval < 0)
        {
            // リトライ間隔
            throw new ArgumentOutOfRangeException("retryInterval");
        }
        if (action == null)
        {
            // 処理
            throw new ArgumentNullException("action");
        }

        /// <summary>
        /// リトライ回数をカウントします
        /// </summary>
        int retryCount = 0;

        while (true)
        {
            try
            {
                // メソッド
                return action();
            }
            catch (Exception)
            {
                // リトライが必要な処理で例外が発生した場合
                // 指定回数リトライ
                if (retryCount++ < retryMaxCount)
                {
                    // 最大リトライ回数に達していない場合
                    Thread.Sleep(retryInterval);
                }
                else
                {
                    // 最大リトライ回数に達した場合
                    throw;
                }
            }
        }
    }

    /// <summary>
    /// 処理を実行する
    /// 例外が発生した場合は指定秒数間隔でリトライする
    /// </summary>
    /// <param name="retryMaxCount">最大リトライ回数</param>
    /// <param name="retryInterval">リトライ間隔(ミリ秒)</param>
    /// <param name="action">リトライ対象メソッド(戻り値なし)</param>
    public static void Execute(int retryMaxCount, int retryInterval, Action action)
    {

        // パラメータチェック
        if (retryMaxCount < 0)
        {
            // 最大リトライ回数
            throw new ArgumentOutOfRangeException("retryMaxCount");
        }
        if (retryInterval < 0)
        {
            // リトライ間隔
            throw new ArgumentOutOfRangeException("retryInterval");
        }
        if (action == null)
        {
            // 処理
            throw new ArgumentNullException("action");
        }

        /// <summary>
        /// リトライ回数をカウントします
        /// </summary>
        int retryCount = 0;

        while (true)
        {
            try
            {
                action();
                break;
            }
            catch (Exception)
            {
                // リトライが必要な処理で例外が発生した場合
                // 指定回数リトライ

                if (retryCount++ < retryMaxCount)
                {
                    // 最大リトライ回数に達していない場合
                    Thread.Sleep(retryInterval);
                }
                else
                {
                    // 最大リトライ回数に達した場合
                    throw;
                }
            }
        }
    }
}
```

# 4. 使用方法
## 4.1. リトライ処理メソッド
```C#:
var class1 = new Class1();

// 戻り値なしの場合
RetryExecteUtil.Execute(5,1000,()=> class1.RetryVoidMethod());

// 戻り値ありの場合
Debug.Print(RetryExecteUtil.Execute(5,1000,()=> class1.RetryReturnMethod("OK")));
```

## 4.2. リトライ対象クラス、メソッド
```C#:Class1.cs
public class Class1
{
    int _count1 = 0;
    int _count2 = 0;

    public void RetryVoidMethod()
    {
        _count1++;

        if (_count1 < 5)
        {
            Debug.Print($"Exception {_count1}");
            throw new Exception();
        }
    }

    public string RetryReturnMethod(string s)
    {
        
        _count2++;

        if (_count2 < 5)
        {
            Debug.Print($"Exception {_count2}");
            throw new Exception();
        }

        return s;
    }

}
```

## 4.3 動作確認
```
デバッグ出力で動作を確認した
Exception 1
例外がスローされました: 'System.Exception'
Exception 2
例外がスローされました: 'System.Exception'
Exception 3
例外がスローされました: 'System.Exception' 
Exception 4
例外がスローされました: 'System.Exception'
Exception 1
例外がスローされました: 'System.Exception'
Exception 2
例外がスローされました: 'System.Exception'
Exception 3
例外がスローされました: 'System.Exception'
Exception 4
例外がスローされました: 'System.Exception'
OK
```

# 5. 参考文献

https://tera1707.com/entry/2023/05/02/235027

https://qiita.com/naka123/items/0ee1a0aecc492ead14d0

https://qiita.com/t_takahari/items/6367434c3484b29cf14d