Question

I have a question which is an extension of the following question raised on this site.

Is there a more elegant way to merge observables when return type is unimportant?

I have an IObservable<Unit> (lets say X), a reactive collection (Y) and a property (Z). Return type is not important. I just want to subscribe when any of these change.

I know how to observe all 3 and Subscribe using Observable.Merge as below.

Observable.Merge(X, Y.Changed, ObservableForProperty(Z).Select(_ => Unit.Default)).Subscribe(..)

And it works.

However, when I try to use WhenAny(...,....,....).Subscribe(), the subscribe does not get triggered when my X changes. What is the syntax for doing the above using WhenAny(...) rather than Observable.Merge(..)??

I prefer to use WhenAny(....) because I am using ReactiveUI in other places.

Example: Say I've got a class derived from ReactiveObject with following properties.

public class AnotherVM : ReactiveObject
{
    public bool IsTrue
    {
        get { return this.isTrue; }
        set { this.RaiseAndSetIfChanged(x => x.isTrue, ref this.isTrue, value); }
    }

    public IObservable<Unit> Data
    {
        get { return this.data; }
    }

    public ReactiveCollection MyCol
    {
       get { return Mycol; }
    }    
}

public class MyVM : ReactiveObject
{
    MyVM
    {
       // do WhenAny or Observable.Merge here....
    }
}

I want to observe the above properties in AnotherVM class using Observable.Merge(..) or WhenAny(...) in MyVM class. I found that I do not always get a notification when I subscribe to the above in MyVM using WhenAny(...) or Merge(...) when either of the 3 properties change.

Was it helpful?

Solution

WhenAny is not for monitoring across sets of arbitrary observables, it's for monitoring the properties of an object supported by ReactiveUI (like a ReactiveObject or reactive collection).

For the general case of combining changes in observable streams, Observable.Merge is the right way to go.

EDIT

I note that you have declared the Data and MyCol properties read only. If you use a Merge like this:

Observerable.Merge(this.WhenAnyValue(o=>o.IsTrue, v=>Unit.Default),
                   this.Data,
                   this.MyCol.CollectionChanged.Select(v=>Unit.Default))

...then you must be careful not to change the backing fields. If you do, then you will get missing events - maybe this is what is happening?

In that case you would need to wire up those properties to RaiseAndSetIfChanged and use a Switch to keep track - e.g. if this.data could change then you would need (I'm using ReactiveUI 5 + .NET 4.5 here in case the RaiseAndSetIfChanged syntax looks odd):

public IObservable<Unit> Data
{
    get { return this.data; }
    private set { this.RaiseAndSetIfChanged(ref data, value); }
}

and your merge would be something like:

Observerable.Merge(this.WhenAnyValue(o=>o.IsTrue, v=>Unit.Default),
                   this.WhenAnyObservable(x => x.Data),
                   this.MyCol.CollectionChanged.Select(v=>Unit.Default))

WhenAnyObservable is conceptually equivalent to this:

WhenAny(x => x.Data, vm => vm.Value).Switch()

using Switch to flip over to the latest value of Data when it changes. Don't forget to use the setter to change values of data!

OTHER TIPS

This should do it.

IObservable<Unit> merged =
    Observerable.Merge
    ( this.WhenAnyValue(o=>o.IsTrue, v=>Unit.Default)
    , this.Data
    , this.MyCol.CollectionChanged.Select(v=>Unit.Default)
    )

Theoretically you could write a special version of merge that would disregard the type of the observable and return IObservable<Unit>. Then you could write

IObservable<Unit> merged =
    Observerable.MergeToUnit
    ( this.WhenAnyValue(o=>o.IsTrue)
    , this.Data
    , this.MyCol.CollectionChanged
    )

but then you would need many overloads of MergeToUnit for up to the N parameters you would like to support.

The most general pattern to use with WhenAny with multiple objects is

Observable.CombineLatest
  ( source0.WhenAnyValue(s=>s.FieldA)
  , source1.WhenAnyValue(s=>s.FieldB)
  , source2.WhenAnyValue(s=>s.FieldC)
  , source3.WhenAnyValue(s=>s.FieldD)
  , (a,b,c,d) => Process(a,b,c,d)
  )

It's sometimes just better to get used to using the standard combinators.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top