エラーが起こった時にリトライする を一般化したコードを書いてみる


エラーが起こった時にリトライする を一般化したコードを書いてみる

そのままです!
いろんなケースはあると思うのですがリトライ処理を毎回書くのも面倒くさいので, 一般化してみましょう。
書いてみましょう。

エラー用メソッド

非同期用asyncも用意しました
何も入れないでコンソールを流すと成功何か入れると例外が出ます。

        /// <summary>
        /// エラーを出すメソッド
        /// </summary>
        public void Foo()
        {
            Console.WriteLine("何も入れないと成功するよ");
            var l = Console.ReadLine();
            if(string.IsNullOrWhiteSpace(l))
            {
                Console.WriteLine("完了したよ");
                return;

            }
            throw new Exception("エラー出たよ");

        }

        /// <summary>
        /// エラーを出すメソッドAsync
        /// </summary>
        public async Task<int> FooAsync()
        {
            Console.WriteLine("何も入れないと成功するよ Task");
            var l = Console.ReadLine();
            if (string.IsNullOrWhiteSpace(l))
            {
                Console.WriteLine("完了したよ");
                return 0;

            }
            throw new Exception("エラー出たよ");

        }

一番単純なリトライ

エラーが出たら再帰呼び出しするのが一番簡単なエラーでしょう

        /// <summary>
        /// エラーをしたらリトライする
        /// </summary>
        public void Simple()
        {
            try
            {
                Foo();
            }
            catch
            {
                Simple();
            }
        }

さらにリトライをするかどうかのチェックを入れるとこうなります

        /// <summary>
        /// エラーをしたらconditionをチェックしてリトライする
        /// </summary>
        public void SimpleWithCondition()
        {
            try
            {
                Foo();
            }
            catch
            {
                if (Condition())
                {
                    SimpleWithCondition();
                }
            }
        }

Conditionはこんな感じ


        /// <summary>
        /// エラーが出た後リトライできるかのチェック
        /// </summary>
        /// <returns></returns>
        public bool Condition()
        {
            Console.WriteLine("エラー出たけどリトライする?(空行だとリトライ)");
            var l = Console.ReadLine();
            if (string.IsNullOrWhiteSpace(l))
            {
                Console.WriteLine("リトライするよ");
                return true;

            }
            Console.WriteLine("停止するよ");
            return false;
        }

FooをFooAsyncに置き換えてみる

        /// <summary>
        /// SimpleWithConditionの非同期
        /// </summary>
        public async Task WithConditionAsync()
        {
            try
            {
                await FooAsync();
            }
            catch
            {
                if (Condition())
                {
                    await WithConditionAsync();
                }
            }
        }

ConditionもTask…は芸がないのでIObservableにしてみる

        /// <summary>
        /// SimpleWithConditionの非同期
        /// ConditionはIObservable
        /// </summary>
        public async Task WithConditionObservableAsync()
        {
            try
            {
                await FooAsync();
            }
            catch
            {
                ConditionAsObservable().Subscribe(async b =>
                {
                    if (b)
                    {
                        await WithConditionObservableAsync();
                    }
                });
            }
        }

ConditionAsObservableはこんな感じ

        /// <summary>
        /// エラーが出た後リトライできるかのチェック
        /// </summary>
        /// <returns></returns>
        public IObservable<bool> ConditionAsObservable()
        {
            return Observable.Create<bool>(observer =>
            {
                Console.WriteLine("エラー出たけどリトライする?(空行だとリトライ) Observable ");
                var l = Console.ReadLine();
                if (string.IsNullOrWhiteSpace(l))
                {
                    Console.WriteLine("リトライするよ");
                    observer.OnNext(true);
                    observer.OnCompleted();

                }
                else
                {
                    Console.WriteLine("停止するよ");
                    observer.OnNext(false);
                    observer.OnCompleted();
                }

                return Disposable.Empty;
            });

        }

さらにFooAsyncの結果を戻したい

Conditionをawaitにして…

        /// <summary>
        /// SimpleWithConditionの非同期
        /// ConditionはIObservableで値を返したい
        /// </summary>
        public async Task<int> ValueTaskWithConditionObservableAsync()
        {
            try
            {
               return await FooAsync();
            }
            catch
            {
                if (await ConditionAsObservable())
                {
                    return await ValueTaskWithConditionObservableAsync();
                }

                return 0;
            }
        }

これのFooAsyncとConditionAsObservable,default値を外に出して一般化する

        /// <summary>
        /// SimpleWithConditionの非同期
        /// ConditionはIObservableで値を返したい
        /// FooとConditionはさらに抽象化したい
        /// </summary>
        public async Task<T> ValueTaskWithConditionObservableAbstractionAsync<T>(Func<Task<T>> foo,Func<Task<bool>> condition,T  @default = default)
        {
            try
            {
                return await foo();
            }
            catch
            {
                if (await condition())
                {
                    return await ValueTaskWithConditionObservableAbstractionAsync(foo,condition,@default);
                }

                return @default;
            }
        }

なぜFuncを引数にしているかというとリトライするため

コード全体

using System;
using System.Diagnostics;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Reactive.Threading.Tasks;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;

namespace ErrorRetrys
{
    class Program
    {
        static  void Main(string[] args)
        {
            Subject<int> s = new Subject<int>();
            s.Subscribe(Console.WriteLine, e => Console.WriteLine(e.Message));
            var p = new Program();
            p.Start().Wait();


        }

        public async Task Start()
        {
            Console.WriteLine("どれを実行する?");
            var k = Console.ReadLine();
            switch (k.FirstOrDefault())
            {
                case '1':
                    Foo();
                    break;
                case '2':
                    Simple();
                    break;
                case '3':
                    SimpleWithCondition();
                    break;
                case '4':
                    await WithConditionAsync();
                    break;
                case '5':
                    await WithConditionObservableAsync();
                    break;
                case '6':
                    var v = await ValueTaskWithConditionObservableAsync();
                    Console.WriteLine(v);
                    break;
                case '7':
                    var v7 = await ValueTaskWithConditionObservableAbstractionAsync(async ()=>await FooAsync(),()=>ConditionAsObservable().ToTask(),100);
                    Console.WriteLine(v7);
                    break;
                default:
                    Console.WriteLine("この中から選んでね" + Environment.NewLine + @"
                case '1': Foo();
                case '2': Simple();
                case '3': SimpleWithCondition();
                case '4': await WithConditionAsync();
                case '5': await WithConditionObservableAsync();
                case '6': await ValueTaskWithConditionObservableAsync();
                case '7': await ValueTaskWithConditionObservableAbstractionAsync(FooAsync(),ConditionAsObservable().ToTask(),100);
");
                    await Start();
                    return;

            }

            Console.WriteLine("もう一回するなら空行を入れて");
            if (string.IsNullOrWhiteSpace(Console.ReadLine()))
            {
                await Start();
            }
        }

        /// <summary>
        /// エラーを出すメソッド
        /// </summary>
        public void Foo()
        {
            Console.WriteLine("何も入れないと成功するよ");
            var l = Console.ReadLine();
            if(string.IsNullOrWhiteSpace(l))
            {
                Console.WriteLine("完了したよ");
                return;

            }
            throw new Exception("エラー出たよ");

        }

        /// <summary>
        /// エラーを出すメソッドAsync
        /// </summary>
        public async Task<int> FooAsync()
        {
            Console.WriteLine("何も入れないと成功するよ Task");
            var l = Console.ReadLine();
            if (string.IsNullOrWhiteSpace(l))
            {
                Console.WriteLine("完了したよ");
                return 0;

            }
            throw new Exception("エラー出たよ");

        }

        /// <summary>
        /// エラーが出た後リトライできるかのチェック
        /// </summary>
        /// <returns></returns>
        public bool Condition()
        {
            Console.WriteLine("エラー出たけどリトライする?(空行だとリトライ)");
            var l = Console.ReadLine();
            if (string.IsNullOrWhiteSpace(l))
            {
                Console.WriteLine("リトライするよ");
                return true;

            }
            Console.WriteLine("停止するよ");
            return false;
        }

        /// <summary>
        /// エラーが出た後リトライできるかのチェック
        /// </summary>
        /// <returns></returns>
        public IObservable<bool> ConditionAsObservable()
        {
            return Observable.Create<bool>(observer =>
            {
                Console.WriteLine("エラー出たけどリトライする?(空行だとリトライ) Observable ");
                var l = Console.ReadLine();
                if (string.IsNullOrWhiteSpace(l))
                {
                    Console.WriteLine("リトライするよ");
                    observer.OnNext(true);
                    observer.OnCompleted();

                }
                else
                {
                    Console.WriteLine("停止するよ");
                    observer.OnNext(false);
                    observer.OnCompleted();
                }

                return Disposable.Empty;
            });

        }

        /// <summary>
        /// エラーをしたらリトライする
        /// </summary>
        public void Simple()
        {
            try
            {
                Foo();
            }
            catch
            {
                Simple();
            }
        }

        /// <summary>
        /// エラーをしたらconditionをチェックしてリトライする
        /// </summary>
        public void SimpleWithCondition()
        {
            try
            {
                Foo();
            }
            catch
            {
                if (Condition())
                {
                    SimpleWithCondition();
                }
            }
        }

        /// <summary>
        /// SimpleWithConditionの非同期
        /// </summary>
        public async Task WithConditionAsync()
        {
            try
            {
                await FooAsync();
            }
            catch
            {
                if (Condition())
                {
                    await WithConditionAsync();
                }
            }
        }

        /// <summary>
        /// SimpleWithConditionの非同期
        /// ConditionはIObservable
        /// </summary>
        public async Task WithConditionObservableAsync()
        {
            try
            {
                await FooAsync();
            }
            catch
            {
                ConditionAsObservable().Subscribe(async b =>
                {
                    if (b)
                    {
                        await WithConditionObservableAsync();
                    }
                });
            }
        }


        /// <summary>
        /// SimpleWithConditionの非同期
        /// ConditionはIObservableで値を返したい
        /// </summary>
        public async Task<int> ValueTaskWithConditionObservableAsync()
        {
            try
            {
               return await FooAsync();
            }
            catch
            {
                if (await ConditionAsObservable())
                {
                    return await ValueTaskWithConditionObservableAsync();
                }

                return 0;
            }
        }


        /// <summary>
        /// SimpleWithConditionの非同期
        /// ConditionはIObservableで値を返したい
        /// FooとConditionはさらに抽象化したい
        /// </summary>
        public async Task<T> ValueTaskWithConditionObservableAbstractionAsync<T>(Func<Task<T>> foo,Func<Task<bool>> condition,T  @default = default)
        {
            try
            {
                return await foo();
            }
            catch
            {
                if (await condition())
                {
                    return await ValueTaskWithConditionObservableAbstractionAsync(foo,condition,@default);
                }

                return @default;
            }
        }
    }
}