/// <summary>
        /// Initializes a new instance of the RetryPolicy class with the specified number of retry attempts and parameters defining the progressive delay between retries.
        /// </summary>
        /// <param name="errorDetectionStrategy">The <see cref="ITransientErrorDetectionStrategy"/> that is responsible for detecting transient conditions.</param>
        /// <param name="retryStrategy">The retry strategy to use for this retry policy.</param>
        public RetryPolicy(ITransientErrorDetectionStrategy errorDetectionStrategy, RetryStrategy retryStrategy)
        {
            ErrorDetectionStrategy = errorDetectionStrategy;

            if (errorDetectionStrategy == null)
            {
                throw new InvalidOperationException("The error detection strategy type must implement the ITransientErrorDetectionStrategy interface.");
            }

            RetryStrategy = retryStrategy;
        }
        /// <summary>
        /// Repetitively executes the specified action while it satisfies the current retry policy.
        /// </summary>
        /// <typeparam name="TResult">The type of result expected from the executable action.</typeparam>
        /// <param name="func">A delegate representing the executable action which returns the result of type R.</param>
        /// <returns>The result from the action.</returns>
        public async virtual Task <TResult> ExecuteAction <TResult>(Func <Task <TResult> > func)
        {
            int       retryCount = 0;
            TimeSpan  delay      = TimeSpan.Zero;
            Exception lastError;

            var shouldRetry = RetryStrategy.GetShouldRetry();

            for (; ;)
            {
                lastError = null;

                try
                {
                    return(await func().ConfigureAwait(false));
                }
                catch (Exception ex)
                {
                    lastError = ex;

                    if (!(ErrorDetectionStrategy.IsTransient(lastError) && shouldRetry(retryCount++, lastError, out delay)))
                    {
                        throw;
                    }
                }

                // Perform an extra check in the delay interval. Should prevent from accidentally ending up with the value of -1 that will block a thread indefinitely.
                // In addition, any other negative numbers will cause an ArgumentOutOfRangeException fault that will be thrown by Thread.Sleep.
                if (delay.TotalMilliseconds < 0)
                {
                    delay = TimeSpan.Zero;
                }

                OnRetrying(retryCount, lastError, delay);

                if (retryCount > 1 || !RetryStrategy.FastFirstRetry)
                {
                    await Task.Delay(delay).ConfigureAwait(false);
                }
            }
        }