Skip to content
Matthew Adams By Matthew Adams Co-Founder
RX and INotifyPropertyChanged

Richard Szalay has a great blog post which allows you to use the Reactive Extensions IObservable pattern to subscribe to INotifyPropertyChanged implementers.

However, the syntax is still a little bit clumsy.

By adding a few extra helpers to the class we can go from this:

viewModel.GetPropertyChangeValues(x => x.TheProperty).Subscribe(GetToWork);

To this

viewModel.Subscribe(x => x.SomeProperty, SomePropertyChanged);
Programming C# 12 Book, by Ian Griffiths, published by O'Reilly Media, is now available to buy.

Here's my slightly modified code.

public static class NotifyPropertyChangeReactiveExtensions
{
    // Returns the values of property (an Expression) as they
    // change, starting with the current value
    public static IObservable<TValue>
        GetPropertyValues<TSource, TValue>(
            this TSource source,
            Expression<Func<TSource, TValue>> property)
            where TSource : INotifyPropertyChanged
    {
        MemberExpression memberExpression =
            property.Body as MemberExpression;

        if (memberExpression == null)
        {
            throw new ArgumentException(
                "property must directly access " +
                "a property of the source");
        }

        string propertyName = memberExpression.Member.Name;

        Func<TSource, TValue> accessor = property.Compile();

        return source.GetPropertyChangedEvents()
            .Where(x => x.EventArgs.PropertyName == propertyName)
            .Select(x => accessor(source))
            .StartWith(accessor(source));
    }

    // This is a wrapper around FromEvent(PropertyChanged)
    public static IObservable<IEvent<PropertyChangedEventArgs>>
        GetPropertyChangedEvents(this INotifyPropertyChanged source)
    {
        return Observable.FromEvent<PropertyChangedEventHandler,
            PropertyChangedEventArgs>(
            h => new PropertyChangedEventHandler(h),
            h => source.PropertyChanged += h,
            h => source.PropertyChanged -= h);
    }

    public static IDisposable
        Subscribe<TSource, TValue>(
            this TSource source,
            Expression<Func<TSource, TValue>> property,
            Action<TValue> observer)
        where TSource : INotifyPropertyChanged
    {
        return source
                .GetPropertyValues(property)
                .Subscribe(observer);
    }

    public static IDisposable
        Subscribe<TSource, TValue>(
            this TSource source,
            Expression<Func<TSource, TValue>> property,
            Action<TValue> observer,
            Action onCompleted)
        where TSource : INotifyPropertyChanged
    {
        return source
                .GetPropertyValues(property)
                .Subscribe(observer, onCompleted);
    }

    public static IDisposable
        Subscribe<TSource, TValue>(
            this TSource source,
            Expression<Func<TSource, TValue>> property,
            Action<TValue> observer,
            Action<Exception> onException)
        where TSource : INotifyPropertyChanged
    {
        return source
                .GetPropertyValues(property)
                .Subscribe(observer, onException);
    }

    public static IDisposable
        Subscribe<TSource, TValue>(
            this TSource source,
            Expression<Func<TSource, TValue>> property,
            Action<TValue> observer,
            Action<Exception> onException,
            Action onCompleted)
        where TSource : INotifyPropertyChanged
    {
        return source
                .GetPropertyValues(property)
                .Subscribe(observer, onException, onCompleted);
    }
}
The Introduction to Rx.NET 2nd Edition (2024) Book, by Ian Griffiths & Lee Campbell, is now available to download for FREE.

Why should you layer your APIs like this? Read on here...

Matthew Adams on Twitter

Matthew Adams

Co-Founder

Matthew Adams

Matthew was CTO of a venture-backed technology start-up in the UK & US for 10 years, and is now the co-founder of endjin, which provides technology strategy, experience and development services to its customers who are seeking to take advantage of Microsoft Azure and the Cloud.