337 lines
12 KiB
C#
337 lines
12 KiB
C#
// Licensed to the .NET Foundation under one or more agreements.
|
|
// The .NET Foundation licenses this file to you under the MIT License.
|
|
// See the LICENSE file in the project root for more information.
|
|
|
|
using System.Collections.Generic;
|
|
using System.Reactive.Disposables;
|
|
using System.Threading;
|
|
|
|
namespace System.Reactive.Linq.ObservableImpl
|
|
{
|
|
internal static class SequenceEqual<TSource>
|
|
{
|
|
internal sealed class Observable : Producer<bool, Observable._>
|
|
{
|
|
private readonly IObservable<TSource> _first;
|
|
private readonly IObservable<TSource> _second;
|
|
private readonly IEqualityComparer<TSource> _comparer;
|
|
|
|
public Observable(IObservable<TSource> first, IObservable<TSource> second, IEqualityComparer<TSource> comparer)
|
|
{
|
|
_first = first;
|
|
_second = second;
|
|
_comparer = comparer;
|
|
}
|
|
|
|
protected override _ CreateSink(IObserver<bool> observer) => new(_comparer, observer);
|
|
|
|
protected override void Run(_ sink) => sink.Run(this);
|
|
|
|
internal sealed class _ : IdentitySink<bool>
|
|
{
|
|
private readonly IEqualityComparer<TSource> _comparer;
|
|
private readonly object _gate;
|
|
private readonly Queue<TSource> _ql;
|
|
private readonly Queue<TSource> _qr;
|
|
|
|
public _(IEqualityComparer<TSource> comparer, IObserver<bool> observer)
|
|
: base(observer)
|
|
{
|
|
_comparer = comparer;
|
|
_gate = new object();
|
|
_ql = new Queue<TSource>();
|
|
_qr = new Queue<TSource>();
|
|
}
|
|
|
|
private bool _donel;
|
|
private bool _doner;
|
|
|
|
private SingleAssignmentDisposableValue _second;
|
|
|
|
public void Run(Observable parent)
|
|
{
|
|
SetUpstream(parent._first.SubscribeSafe(new FirstObserver(this)));
|
|
_second.Disposable = parent._second.SubscribeSafe(new SecondObserver(this));
|
|
}
|
|
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
if (disposing)
|
|
{
|
|
_second.Dispose();
|
|
}
|
|
|
|
base.Dispose(disposing);
|
|
}
|
|
|
|
private sealed class FirstObserver : IObserver<TSource>
|
|
{
|
|
private readonly _ _parent;
|
|
|
|
public FirstObserver(_ parent)
|
|
{
|
|
_parent = parent;
|
|
}
|
|
|
|
public void OnNext(TSource value)
|
|
{
|
|
lock (_parent._gate)
|
|
{
|
|
if (_parent._qr.Count > 0)
|
|
{
|
|
var equal = false;
|
|
var v = _parent._qr.Dequeue();
|
|
try
|
|
{
|
|
equal = _parent._comparer.Equals(value, v);
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
_parent.ForwardOnError(exception);
|
|
return;
|
|
}
|
|
if (!equal)
|
|
{
|
|
_parent.ForwardOnNext(false);
|
|
_parent.ForwardOnCompleted();
|
|
}
|
|
}
|
|
else if (_parent._doner)
|
|
{
|
|
_parent.ForwardOnNext(false);
|
|
_parent.ForwardOnCompleted();
|
|
}
|
|
else
|
|
{
|
|
_parent._ql.Enqueue(value);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void OnError(Exception error)
|
|
{
|
|
lock (_parent._gate)
|
|
{
|
|
_parent.ForwardOnError(error);
|
|
}
|
|
}
|
|
|
|
public void OnCompleted()
|
|
{
|
|
lock (_parent._gate)
|
|
{
|
|
_parent._donel = true;
|
|
if (_parent._ql.Count == 0)
|
|
{
|
|
if (_parent._qr.Count > 0)
|
|
{
|
|
_parent.ForwardOnNext(false);
|
|
_parent.ForwardOnCompleted();
|
|
}
|
|
else if (_parent._doner)
|
|
{
|
|
_parent.ForwardOnNext(true);
|
|
_parent.ForwardOnCompleted();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private sealed class SecondObserver : IObserver<TSource>
|
|
{
|
|
private readonly _ _parent;
|
|
|
|
public SecondObserver(_ parent)
|
|
{
|
|
_parent = parent;
|
|
}
|
|
|
|
public void OnNext(TSource value)
|
|
{
|
|
lock (_parent._gate)
|
|
{
|
|
if (_parent._ql.Count > 0)
|
|
{
|
|
var equal = false;
|
|
var v = _parent._ql.Dequeue();
|
|
try
|
|
{
|
|
equal = _parent._comparer.Equals(v, value);
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
_parent.ForwardOnError(exception);
|
|
return;
|
|
}
|
|
if (!equal)
|
|
{
|
|
_parent.ForwardOnNext(false);
|
|
_parent.ForwardOnCompleted();
|
|
}
|
|
}
|
|
else if (_parent._donel)
|
|
{
|
|
_parent.ForwardOnNext(false);
|
|
_parent.ForwardOnCompleted();
|
|
}
|
|
else
|
|
{
|
|
_parent._qr.Enqueue(value);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void OnError(Exception error)
|
|
{
|
|
lock (_parent._gate)
|
|
{
|
|
_parent.ForwardOnError(error);
|
|
}
|
|
}
|
|
|
|
public void OnCompleted()
|
|
{
|
|
lock (_parent._gate)
|
|
{
|
|
_parent._doner = true;
|
|
if (_parent._qr.Count == 0)
|
|
{
|
|
if (_parent._ql.Count > 0)
|
|
{
|
|
_parent.ForwardOnNext(false);
|
|
_parent.ForwardOnCompleted();
|
|
}
|
|
else if (_parent._donel)
|
|
{
|
|
_parent.ForwardOnNext(true);
|
|
_parent.ForwardOnCompleted();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
internal sealed class Enumerable : Producer<bool, Enumerable._>
|
|
{
|
|
private readonly IObservable<TSource> _first;
|
|
private readonly IEnumerable<TSource> _second;
|
|
private readonly IEqualityComparer<TSource> _comparer;
|
|
|
|
public Enumerable(IObservable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer)
|
|
{
|
|
_first = first;
|
|
_second = second;
|
|
_comparer = comparer;
|
|
}
|
|
|
|
protected override _ CreateSink(IObserver<bool> observer) => new(_comparer, observer);
|
|
|
|
protected override void Run(_ sink) => sink.Run(this);
|
|
|
|
internal sealed class _ : Sink<TSource, bool>
|
|
{
|
|
private readonly IEqualityComparer<TSource> _comparer;
|
|
|
|
public _(IEqualityComparer<TSource> comparer, IObserver<bool> observer)
|
|
: base(observer)
|
|
{
|
|
_comparer = comparer;
|
|
}
|
|
|
|
private IEnumerator<TSource>? _enumerator;
|
|
|
|
private static readonly IEnumerator<TSource> DisposedEnumerator = MakeDisposedEnumerator();
|
|
|
|
private static IEnumerator<TSource> MakeDisposedEnumerator()
|
|
{
|
|
yield break;
|
|
}
|
|
|
|
public void Run(Enumerable parent)
|
|
{
|
|
//
|
|
// Notice the evaluation order of obtaining the enumerator and subscribing to the
|
|
// observable sequence is reversed compared to the operator's signature. This is
|
|
// required to make sure the enumerator is available as soon as the observer can
|
|
// be called. Otherwise, we end up having a race for the initialization and use
|
|
// of the _rightEnumerator field.
|
|
//
|
|
try
|
|
{
|
|
var enumerator = parent._second.GetEnumerator();
|
|
|
|
if (Interlocked.CompareExchange(ref _enumerator, enumerator, null) != null)
|
|
{
|
|
enumerator.Dispose();
|
|
return;
|
|
}
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
ForwardOnError(exception);
|
|
|
|
return;
|
|
}
|
|
|
|
SetUpstream(parent._first.SubscribeSafe(this));
|
|
}
|
|
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
if (disposing)
|
|
{
|
|
Interlocked.Exchange(ref _enumerator, DisposedEnumerator)?.Dispose();
|
|
}
|
|
|
|
base.Dispose(disposing);
|
|
}
|
|
|
|
public override void OnNext(TSource value)
|
|
{
|
|
var equal = false;
|
|
|
|
try
|
|
{
|
|
if (_enumerator!.MoveNext()) // NB: Non-null after Run is called.
|
|
{
|
|
var current = _enumerator.Current;
|
|
equal = _comparer.Equals(value, current);
|
|
}
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
ForwardOnError(exception);
|
|
return;
|
|
}
|
|
|
|
if (!equal)
|
|
{
|
|
ForwardOnNext(false);
|
|
ForwardOnCompleted();
|
|
}
|
|
}
|
|
|
|
public override void OnCompleted()
|
|
{
|
|
bool hasNext;
|
|
try
|
|
{
|
|
hasNext = _enumerator!.MoveNext(); // NB: Non-null after Run is called.
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
ForwardOnError(exception);
|
|
return;
|
|
}
|
|
|
|
ForwardOnNext(!hasNext);
|
|
ForwardOnCompleted();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|