/// <summary> /// Returns an observable that contains only the values from the left notification channel /// up to the specified <paramref name="count"/>. /// </summary> /// <typeparam name="TLeft">Type of the left notification channel.</typeparam> /// <typeparam name="TRight">Type of the right notification channel.</typeparam> /// <param name="source">The observable from which values are taken.</param> /// <param name="count">The number of values to take.</param> /// <returns>An observable of values containing the maximum specified number from the left /// notification channel and all values from the right notification channel.</returns> public static IObservable <Either <TLeft, TRight> > TakeLeft <TLeft, TRight>( this IObservable <Either <TLeft, TRight> > source, int count) { Contract.Requires(source != null); Contract.Requires(count >= 0); Contract.Ensures(Contract.Result <IObservable <Either <TLeft, TRight> > >() != null); return(Observable2.CreateEither <TLeft, TRight>( observer => { int remaining = count; return source.SubscribeEither( left => { if (remaining > 0) { remaining--; observer.OnNextLeft(left); if (remaining == 0) { observer.OnCompleted(); } } }, observer.OnNextRight, observer.OnError, observer.OnCompleted); })); }
/// <summary> /// Projects the values from both notification channels into a new sequence. /// </summary> /// <typeparam name="TLeft">Type of the left notification channel.</typeparam> /// <typeparam name="TRight">Type of the right notification channel.</typeparam> /// <typeparam name="TLeftResult">Result type of the left notification channel.</typeparam> /// <typeparam name="TRightResult">Result type of the right notification channel.</typeparam> /// <param name="source">The observable from which values are projected.</param> /// <param name="leftSelector">Projects values from the left notification channel.</param> /// <param name="rightSelector">Projects value from the right notification channel.</param> /// <returns>An observable of results from the projection of values in both notification channels.</returns> public static IObservable <Either <TLeftResult, TRightResult> > Select <TLeft, TRight, TLeftResult, TRightResult>( this IObservable <Either <TLeft, TRight> > source, Func <TLeft, TLeftResult> leftSelector, Func <TRight, TRightResult> rightSelector) { Contract.Requires(source != null); Contract.Requires(leftSelector != null); Contract.Requires(rightSelector != null); Contract.Ensures(Contract.Result <IObservable <Either <TLeftResult, TRightResult> > >() != null); return(Observable2.CreateEither <TLeftResult, TRightResult>( observer => { return source.SubscribeEither( left => observer.OnNextLeft(leftSelector(left)), right => observer.OnNextRight(rightSelector(right)), observer.OnError, observer.OnCompleted); })); }
/// <summary> /// Creates an observable with two pairwise notification channels containing values from the specified /// observable sequence projected by the specified function in the left channel and values projected /// by the other specified function in the right channel. /// </summary> /// <typeparam name="TSource">Type of the notifications in the source sequence.</typeparam> /// <typeparam name="TLeft">Type of the left notification channel.</typeparam> /// <typeparam name="TRight">Type of the right notification channel.</typeparam> /// <param name="source">The observable sequence from which values are projected.</param> /// <param name="leftSelector">Selects a value for the left channel from each value in the specified observable sequence.</param> /// <param name="rightSelector">Selects a value for the right channel from each value in the specified observable sequence.</param> /// <returns>The specified observable sequence projected into paired values produced by the specified selector functions.</returns> public static IObservable <Either <TLeft, TRight> > Pair <TSource, TLeft, TRight>( this IObservable <TSource> source, Func <TSource, TLeft> leftSelector, Func <TSource, TRight> rightSelector) { Contract.Requires(source != null); Contract.Requires(leftSelector != null); Contract.Requires(rightSelector != null); Contract.Ensures(Contract.Result <IObservable <Either <TLeft, TRight> > >() != null); return(Observable2.CreateEither <TLeft, TRight>( observer => { return source.Subscribe( value => { observer.OnNextLeft(leftSelector(value)); observer.OnNextRight(rightSelector(value)); }, observer.OnError, observer.OnCompleted); })); }
/// <summary> /// Creates an observable with two pairwise notification channels from the specified observable sequence /// by choosing which channels will receive each value based on the specified function. /// </summary> /// <typeparam name="TSource">The object that provides notification information.</typeparam> /// <param name="source">The observable from which values will be paired based on the specified selector function.</param> /// <param name="directionSelector">Selects the channels that will receive notifications for every value in the <paramref name="source"/>.</param> /// <returns>An observable sequence with two pairwise notification channels projected from the specified /// observable sequence based on the specified selector function.</returns> public static IObservable <Either <TSource, TSource> > Pair <TSource>( this IObservable <TSource> source, Func <TSource, PairDirection> directionSelector) { return(Observable2.CreateEither <TSource, TSource>( observer => { return source.Subscribe( value => { switch (directionSelector(value)) { case PairDirection.Left: observer.OnNextLeft(value); break; case PairDirection.Right: observer.OnNextRight(value); break; case PairDirection.Both: observer.OnNextLeft(value); observer.OnNextRight(value); break; case PairDirection.Neither: break; default: throw new InvalidOperationException(Errors.InvalidPairDirectionValue); } }, observer.OnError, observer.OnCompleted); })); }
public static IObservable <Either <IObservable <TSource>, TSource> > Introspect <TSource>( this IObservable <TSource> source, IScheduler scheduler) { Contract.Requires(source != null); Contract.Requires(scheduler != null); Contract.Ensures(Contract.Result <IObservable <Either <IObservable <TSource>, TSource> > >() != null); return(Observable2.CreateEither <IObservable <TSource>, TSource>( observer => { var subject = new Subject <Tuple <TSource, ISubject <TSource> > >(); var observations = Subject.Synchronize(subject, scheduler); int pendingOnNext = 0; bool sourceCompleted = false; object gate = new object(); var observationsSubscription = observations.Subscribe( next => { var value = next.Item1; var introspection = next.Item2; try { lock (gate) { observer.OnNextRight(value); } } catch (Exception ex) { introspection.OnError(ex); return; } introspection.OnCompleted(); lock (gate) { if (--pendingOnNext == 0 && sourceCompleted) { observer.OnCompleted(); } } }, ex => { lock (gate) { observer.OnError(ex); } }, () => { lock (gate) { observer.OnCompleted(); } }); var sourceSubscription = source.Subscribe( value => { var introspection = new ReplaySubject <TSource>(1); lock (gate) { observer.OnNextLeft(introspection.AsObservable()); pendingOnNext++; } introspection.OnNext(value); observations.OnNext(Tuple.Create(value, (ISubject <TSource>)introspection)); }, observations.OnError, () => { bool completeNow = false; lock (gate) { sourceCompleted = true; completeNow = pendingOnNext == 0; } if (completeNow) { observations.OnCompleted(); } }); return new CompositeDisposable(sourceSubscription, observationsSubscription, subject); })); }
/// <summary> /// Creates an observable with two pairwise notification channels by combining the latest values of the specified /// observable sequences and choosing which channels will receive values based on the specified function. /// </summary> /// <typeparam name="TLeft">Type of the left notification channel.</typeparam> /// <typeparam name="TRight">Type of the right notification channel.</typeparam> /// <param name="leftSource">The observable sequence that provides notifications for the left channel.</param> /// <param name="rightSource">The observable sequence that provides notifications for the right channel.</param> /// <param name="directionSelector">Selects the channels that will receive notifications for every pair.</param> /// <returns>A new observable sequence containing the latest values in the specified observable sequences.</returns> public static IObservable <Either <TLeft, TRight> > Pair <TLeft, TRight>( this IObservable <TLeft> leftSource, IObservable <TRight> rightSource, Func <TLeft, TRight, PairDirection> directionSelector) { Contract.Requires(leftSource != null); Contract.Requires(rightSource != null); Contract.Requires(directionSelector != null); Contract.Ensures(Contract.Result <IObservable <Either <TLeft, TRight> > >() != null); return(Observable2.CreateEither <TLeft, TRight>( observer => leftSource.Maybe() .CombineLatest( rightSource.Maybe(), (left, right) => new { left, right }) .Subscribe( pair => { if (!pair.left.HasValue) { if (pair.right.HasValue) { observer.OnNextRight(pair.right.Value); } } else if (!pair.right.HasValue) { observer.OnNextLeft(pair.left.Value); } else { var left = pair.left.Value; var right = pair.right.Value; switch (directionSelector(left, right)) { case PairDirection.Left: observer.OnNextLeft(left); break; case PairDirection.Right: observer.OnNextRight(right); break; case PairDirection.Both: observer.OnNextLeft(left); observer.OnNextRight(right); break; case PairDirection.Neither: break; default: throw new InvalidOperationException(Errors.InvalidPairDirectionValue); } } }, observer.OnError, observer.OnCompleted))); }
/// <summary> /// Repeats the source observable sequence when it throws the specified type of exception /// consecutively using the specified back-off algorithm until it produces a value, successfully terminates /// or the specified count has been reached and pairs it with an error channel. /// </summary> /// <typeparam name="TSource">The object that provides notification information.</typeparam> /// <typeparam name="TException">The type of exception to catch.</typeparam> /// <param name="source">The observable to be repeated.</param> /// <param name="consecutiveRetryCount">The maximum number of times to retry the sequence consecutively /// when it's faulted.</param> /// <param name="backOffSelector">Selects the amount of time to delay before repeating when the sequence has faulted.</param>s /// <remarks> /// <see cref="RetryConsecutive{TSource,TException}(IObservable{TSource},int,Func{TException,int,TimeSpan})"/> is appropriate when permanent recovery is required for sequences /// that experience ephemeral consecutive errors at unpredictable intervals, such as those originating /// from network streams. For example, it can produce a sequence that automatically reconnects upon /// consecutive network failures up to the specified <paramref name="consecutiveRetryCount"/> number of /// times; furthermore, if the sequence is able to successfully generate a value after an error, then /// the retry count is reset for subsequent consecutive failures. /// </remarks> /// <returns>The specified observable sequence with an error channel.</returns> /// <seealso href="http://en.wikipedia.org/wiki/Exponential_backoff"> /// Exponential backoff /// </seealso> public static IObservable <Either <TSource, TException> > RetryConsecutive <TSource, TException>( this IObservable <TSource> source, int consecutiveRetryCount, Func <TException, int, TimeSpan> backOffSelector) where TException : Exception { Contract.Requires(source != null); Contract.Requires(consecutiveRetryCount >= 0); Contract.Requires(backOffSelector != null); Contract.Ensures(Contract.Result <IObservable <Either <TSource, TException> > >() != null); return(Observable2.CreateEither <TSource, TException>( observer => { int attemptCount = 1; bool decremented = false; bool resetRequired = false; var sources = Enumerable.Repeat(source, consecutiveRetryCount).GetEnumerator(); return sources .Catch <TSource, TException>( ex => { if (resetRequired) { if (!decremented) /* This behavior matches the Rx behavior of the retryCount parameter in the Retry method. * The first iteration always counts as the first "retry", even though technically it's * not a "retry" because it's first. (If consecutiveRetryCount is set to zero, then the * sequence will end because the enumerator's MoveNext method called below will return false.) */ { Contract.Assume(consecutiveRetryCount > 0); consecutiveRetryCount--; decremented = true; } attemptCount = 1; resetRequired = false; sources = Enumerable.Repeat(source, consecutiveRetryCount).GetEnumerator(); } return sources; }, ex => backOffSelector(ex, attemptCount++)) .SubscribeEither( value => { resetRequired = true; observer.OnNextLeft(value); }, observer.OnNextRight, observer.OnError, observer.OnCompleted); })); }
public static IObservable <Either <TSource, TException> > OnErrorResumeNext <TSource, TException>( this IEnumerator <IObservable <TSource> > sources, Func <TException, IEnumerator <IObservable <TSource> > > handler, Func <TException, TimeSpan> backOffSelector) where TException : Exception { Contract.Requires(sources != null); Contract.Requires(handler != null); Contract.Requires(backOffSelector != null); Contract.Ensures(Contract.Result <IObservable <Either <TSource, TException> > >() != null); return(Observable2.CreateEither <TSource, TException>( observer => { bool movedNext; IObservable <TSource> current = null; Func <bool> moveNext = () => { try { movedNext = sources.MoveNext(); if (movedNext) { current = sources.Current; Contract.Assume(current != null); return true; } } catch (Exception ex) { observer.OnError(ex); } return false; }; if (!moveNext()) { observer.OnCompleted(); return sources; } var subscription = new SerialDisposable(); var sourcesDisposable = new SerialDisposable(); sourcesDisposable.Disposable = sources; var disposable = Scheduler.CurrentThread.Schedule( TimeSpan.Zero, self => { subscription.SetDisposableIndirectly(() => current.Subscribe( observer.OnNextLeft, ex => { var typedError = ex as TException; if (typedError == null) { observer.OnError(ex); } else { observer.OnNextRight(typedError); IEnumerator <IObservable <TSource> > next; try { next = handler(typedError); } catch (Exception ex2) { observer.OnError(ex2); return; } Contract.Assume(next != null); if (sources != next) { sources = next; sourcesDisposable.Disposable = sources; } if (moveNext()) { TimeSpan delay; try { delay = backOffSelector(typedError); } catch (Exception ex2) { observer.OnError(ex2); return; } if (delay < TimeSpan.Zero) /* Feature that allows callers to indicate when an exception is fatal based on its type */ { observer.OnError(ex); } else { self(delay); } } else { observer.OnCompleted(); } } }, () => { if (moveNext()) { self(TimeSpan.Zero); } else { observer.OnCompleted(); } })); }); return new CompositeDisposable(subscription, disposable, sourcesDisposable); })); }