In my C # projects, when implementing GUIs, I often use the ReactiveUI framework.
ReactiveUI is a full-fledged MVVM framework: bindings, routing, message bus, commands and other words that are in the description of almost any MVVM framework, there is here. It can be used almost anywhere where .NET is: WPF, Windows Forms, UWP, Windows Phone 8, Windows Store, Xamarin.
Of course, if you already have experience with it, you will hardly find anything new for yourself here. In this article, we will get acquainted with its basic capabilities regarding working with properties in ViewModel, and in the future, I hope, we will get to other, more interesting and complex features.
Introduction
ReactiveUI is built around a reactive programming model and uses
Reactive Extensions (Rx). However, I don’t set myself the goals to write a guide on reactive programming, only if necessary I will explain how it works. Very soon you will see for yourself that to use the basic features you don’t even need to understand what this beast is: reactive programming. Although you already know him, events are just that. Usually, even in places where “reactivity” is manifested, the code can be quite easy to read and understand what will happen. Of course, if you use the library (and Reactive Extensions) to its fullest, you will have to seriously get acquainted with the reactive model, but for the time being we will go over the basics.
')
Personally, besides the features of ReactiveUI, I like its unobtrusiveness: you can use only a subset of its features, not paying attention to others and not adjusting your application to the framework. Even, for example, apply it side by side with other frameworks, without running into incompatibility. Pretty comfortable.
There is a fly in the ointment. Her name is documentation. She is very bad. There is something
here , but many pages are just stubs, and everything is very dry. There is documentation
here , but the problem is the same: stubs, some copy-paste from the developers' chat, links to sample applications in various sources, descriptions of features of a future version, etc. Developers are quite active in answering questions on StackOverflow, but many questions would not exist if the documentation were normal. However, what is not, is not.
What will be discussed
Let us turn to the specifics. This article will talk about a typical problem with properties in ViewModels, and how it is solved in ReactiveUI. Of course, this problem is the interface INotifyPropertyChanged; a problem that is somehow solved in different ways.
Let's see the classic implementation:
private string _firstName; public string FirstName { get { return _firstName; } set { if (value == _firstName) return; _firstName = value; OnPropertyChanged(); } } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); }
What problems? Yes, like no. Well, three lines in the setter, never mind. In general, I usually write auto-customization and do auto-factoring with resharper in a reduced form, with a minimum of gestures.
But there are still problems. What if you need to synchronize the FullName property when you change FirstName? There are two options: either this is a calculated property and you just need to generate an event about its change, or it should be implemented in the same way as FirstName, and it should be changed. In the first variant, the FirstName property setter will generate the necessary notification:
set { if (value == _firstName) return; _firstName = value; OnPropertyChanged(); OnPropertyChanged(nameof(FullName)); }
In the second, the property will be updated, and it will generate the notification itself:
set { if (value == _firstName) return; _firstName = value; OnPropertyChanged(); UpdateFullName(); } private void UpdateFullName() { FullName = $"{FirstName} {LastName}"; }
It still looks relatively simple, but this is the road to hell. There is also LastName, which must also change FullName. Then we will fasten the search by the entered name, and everything will become even more difficult. And then, and more ... And we find ourselves in a situation where there are continuous event generations in the code, a lot of actions are started from the setters, some errors occur because not all possible execution paths are taken into account or something is caused not in tom okay and other nightmares.
And in general, why does the FirstName property know that FullName is somewhere, and that it is necessary to run a search by name? This is not his concern. It should change and report it. Yes, you can do it, and to call additional actions, you can cling to your own PropertyChanged event, but there is little joy in it - you can sort these events with the name of the changed property in the line.
Yes, and the simple implementation given at the very beginning is still annoying: almost the same code, which you still have to read, into which an error can creep in ...
What does ReactiveUI offer us?
Declarativity and putting dependencies in order.
Install it from Nuget. We are looking for "reactiveui", I put the currently relevant version 6.5.0. Now let's follow the list of available updates and update the Splat that appeared there to the latest version (now 1.6.2). Without this, at one moment everything fell.
Now that we have installed the framework, we will try to improve our first example a little. First we inherit from ReactiveObject and rewrite the property setters:
public class PersonViewModel : ReactiveObject { private string _firstName; public string FirstName { get { return _firstName; } set { this.RaiseAndSetIfChanged(ref _firstName, value); UpdateFullName(); } } private string _lastName; public string LastName { get { return _lastName; } set { this.RaiseAndSetIfChanged(ref _lastName, value); UpdateFullName(); } } private string _fullName; public string FullName { get { return _fullName; } private set { this.RaiseAndSetIfChanged(ref _fullName, value); } } private void UpdateFullName() { FullName = $"{FirstName} {LastName}"; } }
Not a lot yet. Such RaiseAndSetIfChanged could be written by hand. But it is necessary to immediately say that ReactiveObject implements not only INPC:

Here we see, in particular, the implementation of INotifyPropertyChanged, INotifyPropertyChanging and some three IObservable <>.
Read more about the reactive model
Here it is worth saying a few words about what IObservable is. These are reactive (push-based) notification providers. The principle is quite simple: in the classical model (pull-based) we ourselves run to the data providers and poll them for updates. In reactive - we subscribe to such a channel of notifications and do not worry about the survey, all updates will come to us by ourselves:
public interface IObservable<out T> { IDisposable Subscribe(IObserver<T> observer); }
We act as an IObserver <> - observer:
public interface IObserver<in T> { void OnNext(T value); void OnError(Exception error); void OnCompleted(); }
OnNext will be called when the next notification appears. OnError - if an error occurs. OnCompleted - when notifications are over.
At any time, you can unsubscribe from new notifications: for this, the Subscribe method returns a certain IDisposable. You call Dispose - and no new notifications will be received.
Now, if we subscribe to Changed and change FirstName, the OnNext method will be called, and the parameters will contain the same information as in the event PropertyChanged (that is, the reference to the sender and the name of the property).
And also here we have many methods at our disposal, some of which came from LINQ. Select we have already tried. What else can be done? Filter the flow of notifications using Where, make Distinct recurring notifications or DistinctUntilChanged to avoid identical notifications going in a row, use Take, Skip and other LINQ methods.
Let's look at an example of using Rx var observable = Enumerable.Range(1, 4).ToObservable(); observable.Subscribe(Observer.Create<int>( i => Console.WriteLine(i), e => Console.WriteLine(e), () => Console.WriteLine("Taking numbers: complete") ));
Here you can move all these notifications in time and see what works.
It turned out quite briefly, but I think so far this is enough. More can be read, for example,
here or
here .
Associate properties with ReactiveUI
Let's return to the improvement of our problem code. We put in order dependencies:
public class PersonViewModel : ReactiveObject { private string _firstName; public string FirstName { get { return _firstName; } set { this.RaiseAndSetIfChanged(ref _firstName, value); } } private string _lastName; public string LastName { get { return _lastName; } set { this.RaiseAndSetIfChanged(ref _lastName, value); } } private string _fullName; public string FullName { get { return _fullName; } private set { this.RaiseAndSetIfChanged(ref _fullName, value); } } public PersonViewModel(string firstName, string lastName) { _firstName = firstName; _lastName = lastName; this.WhenAnyValue(vm => vm.FirstName, vm => vm.LastName).Subscribe(_ => UpdateFullName()); } private void UpdateFullName() { FullName = $"{FirstName} {LastName}"; } }
See, the properties no longer contain anything extra, all dependencies are described in one place: in the constructor. Here we say to subscribe to the changes FirstName and LastName, and when something changes - call UpdateFullName (). By the way, it can be a little different:
public PersonViewModel(...) { ... this.WhenAnyValue(vm => vm.FirstName, vm => vm.LastName).Subscribe(t => UpdateFullName(t)); } private void UpdateFullName(Tuple<string, string> tuple) { FullName = $"{tuple.Item1} {tuple.Item2}"; }
The notification parameter is a tuple in which the current property values ​​lie. We can transfer them to our full name update method. Although the update method is generally possible to remove and do so:
this.WhenAnyValue(vm => vm.FirstName, vm => vm.LastName).Subscribe(t => { FullName = $"{t.Item1} {t.Item2}"; });
Now let's look at FullName again:
private string _fullName; public string FullName { get { return _fullName; } private set { this.RaiseAndSetIfChanged(ref _fullName, value); } }
Why do we supposedly change the property, when in fact it should be completely dependent on the parts of the name and be read-only? Fix this:
private readonly ObservableAsPropertyHelper<string> _fullName; public string FullName => _fullName.Value; public PersonViewModel(...) { ... _fullName = this.WhenAnyValue(vm => vm.FirstName, vm => vm.LastName) .Select(t => $"{t.Item1} {t.Item2}") .ToProperty(this, vm => vm.FullName); }
ObservableAsPropertyHelper <> helps to implement output properties. Inside there is an IObservable, the property becomes read-only, but when changes are made, notifications are generated.
By the way, besides what came from LINQ, there are other interesting methods for IObservable <>, for example, Throttle:
_fullName = this.WhenAnyValue(vm => vm.FirstName, vm => vm.LastName) .Select(t => $"{t.Item1} {t.Item2}") .Throttle(TimeSpan.FromSeconds(1)) .ToProperty(this, vm => vm.FullName);
Notifications are dropped here, for a second after which the following has followed. That is, as long as the user types something in the name field, FullName will not change. When he stops for at least a second, the full name is updated.
Result
Summary code using System.Reactive.Linq; namespace ReactiveUI.Guide.ViewModel { public class PersonViewModel : ReactiveObject { private string _firstName; public string FirstName { get { return _firstName; } set { this.RaiseAndSetIfChanged(ref _firstName, value); } } private string _lastName; public string LastName { get { return _lastName; } set { this.RaiseAndSetIfChanged(ref _lastName, value); } } private readonly ObservableAsPropertyHelper<string> _fullName; public string FullName => _fullName.Value; public PersonViewModel(string firstName, string lastName) { _firstName = firstName; _lastName = lastName; _fullName = this.WhenAnyValue(vm => vm.FirstName, vm => vm.LastName) .Select(t => $"{t.Item1} {t.Item2}") .ToProperty(this, vm => vm.FullName); } } }
We received ViewModel in which communications between properties are described declaratively, in one place. It seems to me a cool opportunity: you do not need to shovel all the code, trying to understand what will happen when you change certain properties. No side effects - it's pretty obvious. Of course, the result of all these manipulations is some performance degradation. Although this problem should not seriously arise: it is not the core of the system, which should be as productive as possible, but the ViewModel layer.
I hope someone will find this article useful and interesting, and you will try to use the described technologies in your projects. In the future, I hope to describe such things as reactive collections and teams, and then get to more complex examples, which show the interaction between the View and ViewModel layers, routing and user interaction.
Thanks for attention!