diff --git a/src/ReactiveUI.Tests/ObservableAsPropertyHelper/ObservableAsPropertyHelperTest.cs b/src/ReactiveUI.Tests/ObservableAsPropertyHelper/ObservableAsPropertyHelperTest.cs index 0d8e9cee11..f29e1c302a 100644 --- a/src/ReactiveUI.Tests/ObservableAsPropertyHelper/ObservableAsPropertyHelperTest.cs +++ b/src/ReactiveUI.Tests/ObservableAsPropertyHelper/ObservableAsPropertyHelperTest.cs @@ -290,10 +290,62 @@ int GetInitialValue() Assert.Equal(42, result); } - /// - /// Tests that Observable As Property Helpers initial value should emit initial value. - /// - /// The initial value. + /// Test that Observable As Property Helpers defers subscription with initial function value doesn't call on changed when subscribed. + /// The initial value. + [Theory] + [InlineData(default(int))] + [InlineData(42)] + public void OAPHDeferSubscriptionWithInitialFuncValueNotCallOnChangedWhenSubscribed(int initialValue) + { + var observable = Observable.Empty(); + + var wasOnChangingCalled = false; + Action onChanging = v => wasOnChangingCalled = true; + var wasOnChangedCalled = false; + Action onChanged = v => wasOnChangedCalled = true; + + var fixture = new ObservableAsPropertyHelper(observable, onChanged, onChanging, () => initialValue, true); + + Assert.False(fixture.IsSubscribed); + Assert.False(wasOnChangingCalled); + Assert.False(wasOnChangedCalled); + + var result = fixture.Value; + + Assert.True(fixture.IsSubscribed); + Assert.False(wasOnChangingCalled); + Assert.False(wasOnChangedCalled); + Assert.Equal(initialValue, result); + } + + /// Test that Observable As Property Helpers defers subscription with initial function value doesn't call on changed when source provides initial value after subscription. + /// The initial value. + [Theory] + [InlineData(default(int))] + [InlineData(42)] + public void OAPHDeferSubscriptionWithInitialFuncValueNotCallOnChangedWhenSourceProvidesInitialValue(int initialValue) + { + var observable = new Subject(); + + var wasOnChangingCalled = false; + Action onChanging = v => wasOnChangingCalled = true; + var wasOnChangedCalled = false; + Action onChanged = v => wasOnChangedCalled = true; + + var fixture = new ObservableAsPropertyHelper(observable, onChanged, onChanging, () => initialValue, true); + + var result = fixture.Value; + + Assert.Equal(initialValue, result); + + observable.OnNext(initialValue); + + Assert.False(wasOnChangingCalled); + Assert.False(wasOnChangedCalled); + } + + /// Tests that Observable As Property Helpers initial value should emit initial value. + /// The initial value. [Theory] [InlineData(default(int))] [InlineData(42)] diff --git a/src/ReactiveUI/ObservableForProperty/ObservableAsPropertyHelper.cs b/src/ReactiveUI/ObservableForProperty/ObservableAsPropertyHelper.cs index a0198ab5bf..20a15ec064 100644 --- a/src/ReactiveUI/ObservableForProperty/ObservableAsPropertyHelper.cs +++ b/src/ReactiveUI/ObservableForProperty/ObservableAsPropertyHelper.cs @@ -154,18 +154,23 @@ public ObservableAsPropertyHelper( ex => _thrownExceptions.Value.OnNext(ex)) .DisposeWith(_disposable); - _getInitialValue = getInitialValue!; + _getInitialValue = getInitialValue ??= () => default(T?); if (deferSubscription) { - _lastValue = default; - Source = observable.DistinctUntilChanged(); + // Although there are no subscribers yet, we should skip all the values that are equal getInitialValue() instead of equal default(T?) because + // default(T?) is never accessible anyway when subscriptions are deferred. We're going to assume that the current value is getInitialValue() even + // if it hasn't been evaluated yet + Source = observable.SkipWhile(x => EqualityComparer.Default.Equals(x, getInitialValue() /* Don't use field to avoid capturing this */)) + .DistinctUntilChanged(); } else { _lastValue = _getInitialValue(); - Source = observable.StartWith(_lastValue).DistinctUntilChanged(); - Source.Subscribe(_subject).DisposeWith(_disposable); + Source = observable.StartWith(_lastValue) + .DistinctUntilChanged(); + Source.Subscribe(_subject) + .DisposeWith(_disposable); _activated = 1; } } @@ -184,7 +189,7 @@ public T Value if (localReferenceInCaseDisposeIsCalled is not null) { _lastValue = _getInitialValue(); - Source.StartWith(_lastValue).Subscribe(_subject).DisposeWith(localReferenceInCaseDisposeIsCalled); + Source.Subscribe(_subject).DisposeWith(localReferenceInCaseDisposeIsCalled); } }