RxNET で RetryWhen

RxNET に RetryWhen が見当たりません...[^1]
条件付きリトライができずに困ったので自作してみました。

## 拡張メソッド

IObservable を返すC#の拡張メソッドとやらを実装してメソッドチェーンを維持します。

```csharp:RetryWhenExtension.cs
using System;
using System.Reactive.Linq;

namespace RxNET.Extensions
{
    public static class RetryWhenExtension
    {
        public static IObservable<T> RetryWhen<T>(
            this IObservable<T> source,
            Func<IObservable<Exception>, IObservable<T>> predicate)
        {
            return RetryWhenRecursive(source, predicate);
        }
        
        private static IObservable<T> RetryWhenRecursive<T>(
            IObservable<T> source,
            Func<IObservable<Exception>, IObservable<T>> predicate)
        {
            return source.Catch((Exception e) =>
            {
                //
                // シーケンスから例外が Throw された場合、
                // predicate でリトライするかどうかを判定する。
                //
                return
                     predicate(Observable.Return(e))
                    .Catch((Exception ee) =>
                     {
                        //
                        // predicate から例外が Throw された場合、
                        // 後続のシーケンスに例外を流す（＝リトライせずオブザーバーに例外を捕捉させる）。
                        //
                        return Observable.Throw<T>(e);
                     })
                    .SelectMany(_ =>
                     {
                        //
                        // predicate から例外が Throw されなかった場合、
                        // 再度、起点となったシーケンスに繋げる（＝リトライ）。
                        //
                        return RetryWhenRecursive(source, predicate);
                     });
            });
        }
    }
}
```

## 試してみる

5回ごとにしか成功しないメソッドを、1秒おきに最大10回リトライします。

```csharp:Progran.cs
using System;
using System.Linq;
using System.Reactive.Linq;

using RxNET.Extensions;

namespace RetryApp
{
    class MainClass
    {
        const int RETRY_MAX  = 10;
        const int RETRY_WAIT = 1;
        
        public static void Main(string[] args)
        {
            var service = new GreetingService();
            
            int retryAttempt = 0;

            service.Greet()
                   .RetryWhen(errors => {
                        return errors.SelectMany(error => {
                            if (error is NeedToRetryException && ++retryAttempt <= RETRY_MAX) {
                                return Observable.Return(String.Empty).Delay(TimeSpan.FromSeconds(RETRY_WAIT));
                            }
                            return Observable.Throw<string>(error);
                        });
                    })
                   .Subscribe(
                        greet => Console.WriteLine("OnNext: {0}", greet),
                        error => Console.WriteLine("OnError: {0}", error.Message),
                        ()    => Console.WriteLine("OnCompleted:")
                  );
                      
            Console.ReadKey();
        }
    }

    class GreetingService
    {
        int attempt = 0;

        public IObservable<string> Greet()
        {
            return Observable.Defer(() => {
                Console.WriteLine("Defer [{0}]", ++attempt);
                if (attempt % 5 == 0) {
                    return Observable.Return("Hello");
                }
                return Observable.Throw<string>(new NeedToRetryException("Zzz..."));
            });
        }
    }

    class NeedToRetryException : Exception
    {
        public NeedToRetryException() : base() { }
        public NeedToRetryException(string message) : base(message) { }
        public NeedToRetryException(string message, Exception inner) : base(message, inner) { }
    }
}
```

これを実行してみた結果が↓です。
5回目（リトライ4回目）で成功して、処理完了しています。

```
Defer [1]
Defer [2]
Defer [3]
Defer [4]
Defer [5]
OnNext: Hello
OnCompleted:
```

ちなみに RETRY_MAX を 3 にしてみると、

```
Defer [1]
Defer [2]
Defer [3]
Defer [4]
OnError: Zzz...
```

4回目（リトライ3回目）で、成功を待たずに、エラー終了しています。

どうやら期待通りに動いている感じです。

とりあえずは以上です。

## 今回作成したコード
https://github.com/sudachi808/RxNET.RetryWhen

## こちらを参考にしました

http://ufcpp.net/study/csharp/sp3_extension.html
http://safx-dev.blogspot.jp/2015/12/rxswiftretryretrywhen.html

<!-- 注釈 -->
[^1]: [ReactiveX - Retry operator](http://reactivex.io/documentation/operators/retry.html)