Rx INotifyPropertyChanged для IObservable <Tuple <TProperty, TProperty >> - PullRequest
1 голос
/ 07 декабря 2011

Это, должно быть, случай с плохим поиском с моей стороны, так как я знаю, что видел решение для этого на wbe раньше, но как мне поступить с реализацией метода расширения, который способен конвертировать INotifyPropertyChanged.PropertyChanged события вIObservable<Tuple<TProperty,TProperty>> где значения кортежа представляют oldValue и newValue свойства?

Итак, я хочу знать, как лучше всего взять что-то вроде этого: ( укажите ниже )

public static IObservable<TProperty> ObservePropertyChanged<TNotifier, TProperty>(this TNotifier notifier,
    Expression<Func<TNotifier, TProperty>> 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<PropertyChangedEventHandler, PropertyChangedEventArgs>(
        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;
}

и преобразуйте его в соответствии с этой подписью:

public static IObservable<Tuple<TProperty,TProperty>> ObservePropertyChanged<TNotifier, TProperty>(this TNotifier notifier,
    Expression<Func<TNotifier, TProperty>> 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<PropertyChangedEventHandler, PropertyChangedEventArgs>(
        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();
}

Редактировать: Iвыяснил что-то, что, кажется, работает после попытки многих разных операторов.Это правильный способ сделать это?Я что-то упускаю?

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

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

}

Редактировать: Я включил решение Гедеона, чтобы получить следующее:

public static IObservable<Tuple<TProperty, TProperty>> ObserveValueChanged2<TNotifier, TProperty>(this TNotifier notifier,
    Expression<Func<TNotifier, TProperty>> 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<Tuple<TProperty, TProperty>>(obs => {
        Tuple<TProperty, TProperty> 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);
    });
}

PS Iв конце концов наткнулся на мое текущее решение, но я не хочу нарушать этику для SO, должен ли я добавить ответ или закрыть вопрос (я бы предпочел не удалять, поскольку это может оказаться полезным позже)?Я до сих пор не уверен, что это лучший способ сделать это .

1 Ответ

3 голосов
/ 07 декабря 2011

Если вы хотите придерживаться существующих операторов, Zip вместе с Skip, вероятно, будет ближе всего к тому, что вам нужно.Я, вероятно, написал бы это сам, как это (поднимая, где вы бросаете NotImplemented):

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

return Observable.Create<Tuple<TProperty, TProperty>>(obs =>
    {
        Tuple<TProperty, TProperty> 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);
    });
...