Rx INotifyProperty Veranderd aan IObservable <Tuple <TProperty, TProperty >>

This must be a case of poor googling on my part as I know I saw a solution for this out on the wbe before, but how would I go about implementing an extension method which is able to convert INotifyPropertyChanged.PropertyChanged events into an IObservable> where the values of the tuple represent the oldValue and the newValue of the property?

So I want to know what is the best way to take something like this: (credit for below to)

public static IObservable ObservePropertyChanged(this TNotifier notifier,
    Expression> propertyAccessor,
    bool startWithCurrent = false)
    where TNotifier : INotifyPropertyChanged {

   //Parse the expression to find the correct property name.
    MemberExpression member = (MemberExpression)propertyAccessor.Body;
    string name = member.Member.Name;

   //Compile the expression so we can run it to read the property value.
    var reader = propertyAccessor.Compile();

    var propertyChanged = Observable.FromEventPattern(
        handler => (sender, args) => handler(sender, args),
        x => notifier.PropertyChanged += x,
        x => notifier.PropertyChanged -= x);

   //Filter the events to the correct property name, then select the value of the property from the notifier.
    var newValues = from p in propertyChanged
                    where p.EventArgs.PropertyName == name
                    select reader(notifier);

   //If the caller wants the current value as well as future ones, use Defer() so that the current value is read when the subscription
   //is added, rather than right now. Otherwise just return the above observable.
    return startWithCurrent ? Observable.Defer(() => Observable.Return(reader(notifier)).Concat(newValues)) : newValues;
}

En converteer deze naar deze handtekening:

public static IObservable> ObservePropertyChanged(this TNotifier notifier,
    Expression> propertyAccessor,
    bool startWithCurrent = false)
    where TNotifier : INotifyPropertyChanged {

   //Parse the expression to find the correct property name.
    MemberExpression member = (MemberExpression)propertyAccessor.Body;
    string name = member.Member.Name;

   //Compile the expression so we can run it to read the property value.
    var reader = propertyAccessor.Compile();

    var propertyChanged = Observable.FromEventPattern(
        handler => (sender, args) => handler(sender, args),
        x => notifier.PropertyChanged += x,
        x => notifier.PropertyChanged -= x);

   //Filter the events to the correct property name, then select the value of the property from the notifier.
    var newValues = from p in propertyChanged
                    where p.EventArgs.PropertyName == name
                    select reader(notifier);

    throw new NotImplementedException();
}

Edit: I figured out something that seems to work after trying many different operators. Is this a correct way of accomplishing this? Is there anything I'm missing?

public static IObservable> ObserveValueChanged(this TNotifier notifier,
    Expression> propertyAccessor,
    bool startWithCurrent = false)
    where TNotifier : INotifyPropertyChanged {
    var observable = ObservePropertyChanged(notifier, propertyAccessor, startWithCurrent);

    return observable.Scan(new Tuple(default(TProperty), default(TProperty)),
                    (acc, p) => new Tuple(acc.Item2, p));

}

Edit: I incorporated Gideon's solution to end up with the following:

public static IObservable> ObserveValueChanged2(this TNotifier notifier,
    Expression> propertyAccessor,
    bool startWithCurrent = false)
    where TNotifier : INotifyPropertyChanged {

   //Compile the expression so we can run it to read the property value.
    var reader = propertyAccessor.Compile();

    var newValues = ObservePropertyChanged(notifier, propertyAccessor, false);
    if (startWithCurrent) {
        var capturedNewValues = newValues; //To prevent warning about modified closure
        newValues = Observable.Defer(() => Observable.Return(reader(notifier))
                                .Concat(capturedNewValues));
    }

    return Observable.Create>(obs => {
        Tuple oldNew = null;
        return newValues.Subscribe(v => {
                if (oldNew == null) {
                    oldNew = Tuple.Create(default(TProperty), v);
                } else {
                    oldNew = Tuple.Create(oldNew.Item2, v);
                    obs.OnNext(oldNew);
                }
            },
            obs.OnError,
            obs.OnCompleted);
    });
}

P.S. I eventually stumbled upon my current solution, but I don't want to violate any etiquite for SO, should I add an answer or close the question (I'd prefer not to delete since this may prove useful later)? I'm still not sure this is the best way of doing this.

1
De informatie die u zoekt, is niet beschikbaar in INotifyPropertyChanged, maar er zijn een paar aangepaste oplossingen. ReactiveObject van ReactiveUI heeft deze functionaliteit. Misschien is dat wat je zoekt?
toegevoegd de auteur Anderson Imes, de bron
INotifyPropertyChanged geeft u geen toegang tot de oude waarde. Ik denk niet dat deze gebeurtenis goed past bij wat je probeert te doen
toegevoegd de auteur cadrell0, de bron
Ik zou een aangepast evenement maken en het leven gemakkelijk maken.
toegevoegd de auteur cadrell0, de bron
Ik probeer niet als een eikel te klinken, maar als je al weet wat te doen, waarom stel je dan de vraag? Als je een idee hebt, probeer het en kom dan terug als je vastloopt.
toegevoegd de auteur cadrell0, de bron
Ik herinner me dat er ergens een discussie was over het gebruik van ReplaySubject om de waarden vast te leggen. Dus ik dacht dat ik op de een of andere manier de initiële waarde kon vastleggen en dan altijd een verspringend venster van de oude waarde en de huidige/nieuwe waarde liet zien. Slaat dat ergens op? Ik weet gewoon niet hoe ik het in Rx moet doen.
toegevoegd de auteur Damian, de bron
Een ding om op te merken is dat door middel van de eigenschap uitdrukking van deze extensie ik toegang kan krijgen tot de waarde op het moment van abonnement en op het moment van daaropvolgende gebeurtenissen. Kan ik een Rx-bewerking niet gebruiken om in wezen in de wachtrij 2 te staan ​​en vervolgens te vuren?
toegevoegd de auteur Damian, de bron
Ik stel de vraag omdat ik Rx niet zo goed ken. Ik weet niet of ik moet mergen, samenvoegen, windowen, bufferen of een andere operator gebruiken om te doen wat ik wil doen. Ik probeer ook te proberen op te lossen, maar mijn gebrek aan ervaring is mijn hindernis. Ik hoopte dat iemand met ervaring zou kunnen helpen.
toegevoegd de auteur Damian, de bron

1 antwoord

Als u zich bij bestaande operators wilt houden, is Zip samen met Overslaan waarschijnlijk het dichtst in de buurt van wat u zoekt. Ik zou het waarschijnlijk zelf zo schrijven (oppakken waar je de NotImplemented gooit):

if (startWithCurrent)
{
    newValues = Observable.Defer(() => Observable.Return(reader(notifier))
                          .Concat(newValues));
}

return Observable.Create>(obs =>
    {
        Tuple oldNew = null;
        return newValues.Subscribe(v =>
            {
                if (oldNew == null)
                {
                    oldNew = Tuple.Create(default(TProperty), v);
                }
                else
                {
                    oldNew = Tuple.Create(oldNew.Item2, v);
                    obs.OnNext(oldNew);
                }
            },
            obs.OnError,
            obs.OnCompleted);
    });
3
toegevoegd
Bedankt. Zoiets als dit is precies wat ik zocht.
toegevoegd de auteur Damian, de bron