I need to monitor for changes of nullable Foo, including Foo = null or changes of any property like Foo.Bar = 123.
So I am using this syntax (similar to recommended):
WhenAnyValue(o => o.Foo, o => o.Foo!.Bar, (foo, bar) => (foo, bar))
to subscribe to changes of both Foo and Bar of my view model:
public class VM : ReactiveObject
{
[Reactive]
public Foo? Foo { get; set; }
}
public class Foo : ReactiveObject
{
[Reactive]
public int Bar { get; set; }
}
However, this will run subscriber multiple time with stale values. After Foo = null subscriber is called with previous value of Bar and after Foo = new() subscriber is called twice: with previous value of Bar and with correct one. This can be demonstrated with following code:
var vm = new VM() { Foo = new Foo { Bar = 1 } };
vm.WhenAnyValue(o => o.Foo, o => o.Foo!.Bar, (foo, bar) => (foo, bar))
.Subscribe(o => Console.WriteLine($"{o.foo?.GetHashCode()} {o.bar}"));
vm.Foo = null; // #1
vm.Foo = new Foo { Bar = 2 }; // #2
The output looks like this:
58225482 1
1 << stale Bar
32347029 1 << stale Bar
32347029 2 << extra call
Instead I want something like this:
58225482 1
0
32347029 2
In other words, I don't want to have stale values and extra subscriber call if possible.
The Foo == null check inside subscriber will solve #1. But maybe there is a different rx solution to such a problem? Or is moving null check outside of subscriber somehow is also ok?
As for #2, I have only idea of workaround with storing previous value of Foo and ignore first call if previous value was null. Again, it's a procedural way of programming. How this should be done in rx world of programming?
To demonstrate the problem more clearly I have increased the number Foo properties:
public class Foo : ReactiveObject
{
[Reactive]
public int Bar { get; set; }
[Reactive]
public int Baz { get; set; }
[Reactive]
public int Qux { get; set; }
}
Then the output of
var vm = new VM();
vm.WhenAnyValue(o => o.Foo, o => o.Foo!.Bar, o => o.Foo!.Baz, o => o.Foo!.Qux,
(foo, bar, baz, qux) => (foo, bar, baz, qux)).Subscribe(o =>
{
Console.WriteLine($"{o.foo?.GetHashCode()} {o.bar} {o.baz} {o.qux}");
});
vm.Foo = new();
vm.Foo = null;
vm.Foo = new Foo { Bar = 1, Baz = 2, Qux = 3 };
vm.Foo = null;
vm.Foo = new();
will contain lots of stale states:
30631159 0 0 0
0 0 0
56680499 0 0 0
56680499 1 0 0
56680499 1 2 0
56680499 1 2 3
1 2 3
6444509 1 2 3
6444509 0 2 3
6444509 0 0 3
6444509 0 0 0
Here is a fixed version of subscriber:
{
// discard stale state
if (o.foo != null && (o.bar != o.foo.Bar || o.baz != o.foo.Baz || o.qux != o.foo.Qux))
return;
// Console.WriteLine($"{o.foo?.GetHashCode()} {o.bar} {o.baz} {o.qux}");
// use actual null propagated values!
Console.WriteLine($"{o.foo?.GetHashCode()} {o.foo?.Bar} {o.foo?.Baz} {o.foo?.Qux}");
}
Now the output is as expected, one line per change:
30631159 0 0 0
56680499 1 2 3
6444509 0 0 0
In other words:
vm.Foo.Bar = 123;EDIT: The second statement is incorrect, with discard there is no stale state anymore and values are correct, unless subscriber takes too long to finish, but that's another story.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With