VSCodeでC#(例外発生時のリトライ処理)

# はじめに
Visual Studioをインストールして開発しようとすると時間がかかるのでVSCodeでお手軽に
しかし、最新環境では開発していないからHello World動かすだけでもはまるという...


# 参考サイト
◆.NET ダウンロード (Linux、macOS、Windows)
　https://dotnet.microsoft.com/ja-jp/download/dotnet
　　※【.NET6.0】をクリックして、Windowsのx64をダウンロード
　　　dotnet-sdk-6.0.408-win-x64.exe
　　※【.NET7.0】をクリックして、Windowsのx64をダウンロード
　　　dotnet-sdk-7.0.203-win-x64.exe
◆C#の拡張機能(ms-dotnettools.csharp)インストール
　https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csharp
```batch:environment.bat
> dotnet --version
7.0.203
> dotnet new console --use-program-main --target-framework-override net472

※.cs/.csprojを修正

> dotnet run
Hello, World!
```


---
◆ODP.NET＋C#でOracleに接続する
　https://qiita.com/wakasama_pcmaster/items/a81d131f742f0f6e7104
　　※とりあえず試すための環境構築ネタ

---
◆C# コンソール アプリ テンプレートで最上位レベルのステートメントが生成される
　https://learn.microsoft.com/ja-jp/dotnet/core/tutorials/top-level-templates
　　※.NET SDK 6.0.300 以降では、consoleテンプレートにはオプションがあります
　　　--use-program-main
　　　これを使用して、最上位レベルのステートメントを使用せず、
　　　メソッドを持つコンソール プロジェクトを Main 作成します。
◆VSCodeで.NET Frameworkを利用する。－.NET Framework利用のための手続き - Technically Impossible
　https://impsbl.hatenablog.jp/entry/VSCodeWithDNFW2

---
◆[コード]リトライ処理
　https://free-creators.com/retry-proc/
　　※これが一番シンプルだが...
◆C# 再試行パターンを実装する
　https://shikaku-sh.hatenablog.com/entry/c-sharp-implements-retry-pattern
　　※説明～テストまで解説してくれている
◆C#でリトライ処理を共通化してみた
　https://qiita.com/t_takahari/items/6367434c3484b29cf14d
◆エラーが起こった時にリトライする　を一般化したコードを書いてみる
　https://qiita.com/Marimoiro/items/b0282792077ab2966135
◆例外発生時にリトライする
　https://blog.xin9le.net/entry/2013/09/08/173009

---
◆C#でアプリケーション設定を取得・保存する、いくつかの方法
　https://seraphy.hatenablog.com/entry/20120708/p1
◆【C# 10.0】ファイル スコープ名前空間 (一斉置換設定)
　https://ufcpp.net/blog/2021/11/fix-all-file-scoped-namespace/
　　※名前空間を namespace N {} からファイル スコープな namespace N; に書き換え
　　　ImplicitUsings を true にして未使用 using になる部分をごっそり削除
　　※.editorconfig設定で一斉置換可能らしい...

# コード
```xml:RetryOperation.csproj
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net472</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="System.Configuration.ConfigurationManager" Version="7.0.0" />
  </ItemGroup>
</Project>
```

```xml:App.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <appSettings>
        <add key="MaxRetryCount" value="3" />
        <add key="RetrySleepTime" value="5000" />
    </appSettings>
</configuration>
```

```csharp:RetryHelper.cs
namespace RetryOperation {

    using System;
    using System.Configuration;
    
    public static class RetryHelper
    {
        // 条件に合う場合は指定回数以内で再試行し、それ以外の場合はエラー処理
        public static void RetryIfError(Action onAction, Action<Exception> onError, Func<Exception, bool> retryCondition, uint maxRetryCount)
        {
            if (onAction == null)       throw new ArgumentNullException("onAction");
            if (onError == null)        throw new ArgumentNullException("onError");
            if (retryCondition == null) throw new ArgumentNullException("retryCondition");
            RetryHelper.RetryIfErrorCore(onAction, onError, retryCondition, maxRetryCount);
        }

        // コアロジック
        private static void RetryIfErrorCore(Action onAction, Action<Exception> onError, Func<Exception, bool> retryCondition, uint? retryCount)
        {
            if (onError == null)        onError        = ex => {};
            if (retryCondition == null) retryCondition = ex => true;

            int maxRetryCount = int.Parse(ConfigurationManager.AppSettings["MaxRetryCount"]);
            int RetrySleepTime = int.Parse(ConfigurationManager.AppSettings["RetrySleepTime"]);

            for (var retryNumber = 0; retryNumber < maxRetryCount; retryNumber++)
            {
                try
                {
                    onAction();
                }
                catch (Exception ex)
                {
                    if (retryCondition(ex))
                    {
                        if (!retryCount.HasValue)       continue;
                        if (count++ < retryCount.Value) continue;
                    }
                    onError(ex);
                }
            }
        }
    }
}
```

```csharp:Program.cs
namespace RetryOperation {

    using System;
    using System.Runtime.ExceptionServices;

    class Program
    {
        static void Main(string[] args)
        {
            // ループカウンタ
            int retryCount = 0;

            RetryHelper.RetryIfError(() =>
            {
                //--- 失敗したら困る処理

                // DB保存メソッド呼び出し(失敗)
                System.Threading.Thread.Sleep(3000);
                Console.WriteLine("DB保存実行！！{0}回目", ++retryCount);
                if(retryCount < 3){
                    throw new InvalidOperationException();
                }
            },
            exstack =>
            {
                //--- エラー処理
                ExceptionDispatchInfo.Capture(exstack).Throw(); //--- スタックトレース維持
            },
            ex => ex is InvalidOperationException,  //--- 条件
            5);                                     //--- 再試行回数
        }
    }
}
```