[Bug]: `ObservableAsPropertyHelper` emits property changed for lazy initial value after first-time reading and doesn't respect `DistinctUntilChanged`
AmrAlSayed0 opened this issue ยท 1 comments
AmrAlSayed0 commented
Describe the bug ๐
When reading a calculated property backed by a lazy ObservableAsPropertyHelper
an INPC event is emitted while the value of the property is still the initial value which is not the case for normal properties.
There is also another bug that if the emitted non-initial value is the same as the initial value it is still emmitted.
Step to reproduce
public sealed class SuperDuperReactiveObject : ReactiveObject
{
private readonly ObservableAsPropertyHelper<string?> _calculatedPropertyLazyOaph;
private readonly ObservableAsPropertyHelper<string?> _calculatedPropertyEagerOaph;
public string? CalculatedPropertyLazy => this._calculatedPropertyLazyOaph.Value;
public string? CalculatedPropertyEager => this._calculatedPropertyEagerOaph.Value;
[Reactive]
public string? ReactiveStringObject { get; set; }
public SuperDuperReactiveObject(IObservable<string?> calculatedPropertyObs, bool useInternalProperty)
{
if (useInternalProperty)
{
IObservable<string?> reactiveStringObjectObs = this.WhenAnyValue(vm => vm.ReactiveStringObject);
this._calculatedPropertyLazyOaph = reactiveStringObjectObs.ToProperty(this, nameof(SuperDuperReactiveObject.CalculatedPropertyLazy), (string?)null, true);
this._calculatedPropertyEagerOaph = reactiveStringObjectObs.ToProperty(this, nameof(SuperDuperReactiveObject.CalculatedPropertyEager), (string?)null, false);
}
else
{
this._calculatedPropertyLazyOaph = calculatedPropertyObs.ToProperty(this, nameof(SuperDuperReactiveObject.CalculatedPropertyLazy), (string?)null, true);
this._calculatedPropertyEagerOaph = calculatedPropertyObs.ToProperty(this, nameof(SuperDuperReactiveObject.CalculatedPropertyEager), (string?)null, false);
}
}
}
Case 1
Code
Subject<string?> subject = new Subject<string?>();
SuperDuperReactiveObject ro = new SuperDuperReactiveObject(subject, false);
ro.Changed.Subscribe(e => Debug.WriteLine($"{e.PropertyName} Changed to {e.Sender?.GetType()?.GetProperty(e.PropertyName!)?.GetMethod?.Invoke(e.Sender, Array.Empty<object>())}"));
Debug.WriteLine($"Before Sending a source change");
subject.OnNext("First");
Debug.WriteLine($"After Sending a source change");
Debug.WriteLine(string.Empty);
Debug.WriteLine($"Before Reading Properties");
string? calculatedPropertyLazy = ro.CalculatedPropertyLazy;
string? calculatedPropertyEager = ro.CalculatedPropertyEager;
Debug.WriteLine($"After Reading Properties");
Debug.WriteLine(string.Empty);
Debug.WriteLine($"Before Sending a 2nd source change");
subject.OnNext("Second");
Debug.WriteLine($"After Sending a 2nd source change");
Result
Before Sending a source change
CalculatedPropertyEager Changed to First //As expected
After Sending a source change
Before Reading Properties
CalculatedPropertyLazy Changed to //Expected nothing here
After Reading Properties
Before Sending a 2nd source change
CalculatedPropertyEager Changed to Second //As expected
CalculatedPropertyLazy Changed to Second //As expected
After Sending a 2nd source change
Case 2
Code
Subject<string?> subject = new Subject<string?>();
SuperDuperReactiveObject ro = new SuperDuperReactiveObject(subject, false);
ro.Changed.Subscribe(e => Debug.WriteLine($"{e.PropertyName} Changed to {e.Sender?.GetType()?.GetProperty(e.PropertyName!)?.GetMethod?.Invoke(e.Sender, Array.Empty<object>())}"));
Debug.WriteLine($"Before Sending a source change");
subject.OnNext(null);
Debug.WriteLine($"After Sending a source change");
Debug.WriteLine(string.Empty);
Debug.WriteLine($"Before Reading Properties");
string? calculatedPropertyLazy = ro.CalculatedPropertyLazy;
string? calculatedPropertyEager = ro.CalculatedPropertyEager;
Debug.WriteLine($"After Reading Properties");
Debug.WriteLine(string.Empty);
Debug.WriteLine($"Before Sending a 2nd source change");
subject.OnNext(null);
Debug.WriteLine($"After Sending a 2nd source change");
Result
Before Sending a source change
After Sending a source change
Before Reading Properties
CalculatedPropertyLazy Changed to //Expected nothing here
After Reading Properties
Before Sending a 2nd source change
CalculatedPropertyLazy Changed to //Expected nothing here
After Sending a 2nd source change
Case 3
Code
Subject<string?> subject = new Subject<string?>();
SuperDuperReactiveObject ro = new SuperDuperReactiveObject(subject, true);
ro.Changed.Subscribe(e => Debug.WriteLine($"{e.PropertyName} Changed to {e.Sender?.GetType()?.GetProperty(e.PropertyName!)?.GetMethod?.Invoke(e.Sender, Array.Empty<object>())}"));
Debug.WriteLine($"Before Sending a source change");
ro.ReactiveStringObject = "First";
Debug.WriteLine($"After Sending a source change");
Debug.WriteLine(string.Empty);
Debug.WriteLine($"Before Reading Properties");
string? calculatedPropertyLazy = ro.CalculatedPropertyLazy;
string? calculatedPropertyEager = ro.CalculatedPropertyEager;
Debug.WriteLine($"After Reading Properties");
Debug.WriteLine(string.Empty);
Debug.WriteLine($"Before Sending a 2nd source change");
ro.ReactiveStringObject = "Second";
Debug.WriteLine($"After Sending a 2nd source change");
Result
Before Sending a source change
CalculatedPropertyEager Changed to First //As expected
ReactiveStringObject Changed to First //As expected
After Sending a source change
Before Reading Properties
CalculatedPropertyLazy Changed to //Expected nothing here
CalculatedPropertyLazy Changed to First //As expected
After Reading Properties
Before Sending a 2nd source change
CalculatedPropertyEager Changed to Second //As expected
ReactiveStringObject Changed to Second //As expected
CalculatedPropertyLazy Changed to Second //As expected
After Sending a 2nd source change
Case 4
Code
Subject<string?> subject = new Subject<string?>();
SuperDuperReactiveObject ro = new SuperDuperReactiveObject(subject, true);
ro.Changed.Subscribe(e => Debug.WriteLine($"{e.PropertyName} Changed to {e.Sender?.GetType()?.GetProperty(e.PropertyName!)?.GetMethod?.Invoke(e.Sender, Array.Empty<object>())}"));
Debug.WriteLine($"Before Sending a source change");
ro.ReactiveStringObject = null;
Debug.WriteLine($"After Sending a source change");
Debug.WriteLine(string.Empty);
Debug.WriteLine($"Before Reading Properties");
string? calculatedPropertyLazy = ro.CalculatedPropertyLazy;
string? calculatedPropertyEager = ro.CalculatedPropertyEager;
Debug.WriteLine($"After Reading Properties");
Debug.WriteLine(string.Empty);
Debug.WriteLine($"Before Sending a 2nd source change");
ro.ReactiveStringObject = null;
Debug.WriteLine($"After Sending a 2nd source change");
Result
Before Sending a source change
After Sending a source change
Before Reading Properties
CalculatedPropertyLazy Changed to //Expected nothing here
CalculatedPropertyLazy Changed to //Expected nothing here
After Reading Properties
Before Sending a 2nd source change
After Sending a 2nd source change
Reproduction repository
Code is inline
Expected behavior
Included in the comments in the "Steps to reproduce"
IDE
Visual Studio 2022
Operating system
Windows
Device
Windows PC
ReactiveUI Version
19.5.1
github-actions commented
This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.