Nice article. I've been playing around with INPC stuff (again) recently and would like to point out a few things. First, data binding in WPF/SL/WinRT already has field level notification capabilities. We just usually forget about it.
private string name;
public event EventHandler FirstNameChanged;
public string Name
{
set
{
{
this.FirstNameChanged(this, EventArgs.Emtpy); // Not done "correctly", illustration only
}
}
}
This has worked in .NET for data binding and related scenarios since long before WPF, and WPF continues to support it.
As for computed values, Rx can really give you a lot of control here. Here's a fun little example.
public static class BehaviorSubjectExtensions
{
public static void RaisePropertyChanged<TValue>(this IObservable<TValue> self, Expression<Func<TValue>> propertyExpression, Action<string> onPropertyChanged)
{
var f = (propertyExpression as LambdaExpression).Body as MemberExpression;
if (f == null)
{
throw new ArgumentException("Only use expressions that call a single property.", "propertyExpression");
}
public class Person : INotifyPropertyChanged
{
private readonly BehaviorSubject<string> firstName = new BehaviorSubject<string>(string.Empty);
private readonly BehaviorSubject<string> lastName = new BehaviorSubject<string>(string.Empty);
private readonly IObservable<string> fullName;
public Person()
{
this.firstName.RaisePropertyChanged(() => this.FirstName, this.OnPropertyChanged);
this.lastName.RaisePropertyChanged(() => this.LastName, this.OnPropertyChanged);
this.fullName = firstName.CombineLatest(lastName, (first, last) => string.Format("{0} {1}", first, last));
this.fullName.RaisePropertyChanged(() => this.FullName, this.OnPropertyChanged);
}
public string FirstName
{
get { return this.firstName.First(); }
set { this.firstName.OnNext(value); }
}
public string LastName
{
get { return this.lastName.First(); }
set { this.lastName.OnNext(value); }
}
public string FullName
{
get { return this.fullName.First(); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I chose to use INPC in this example, but I could have gone with the observable property pattern instead. Rx gives you a lot more power in how you create computed values, allowing you to change the value based on more than just other properties that expose change notifications. For example, a snippet from ReactiveUI documentation.
DoubleTheString = new ReactiveAsyncCommand(null, 1 /* at a time */);
IObservable<string> doubled_strings = DoubleTheString.RegisterAsyncFunc(x => {
// Pretend to be a slow function
Thread.Sleep(1000);
return string.Format("{0}{0}", x);
}
// ReactiveAsyncCommand will fire its OnNext when the command is *invoked*,
// and doubled_strings will fire when the command *completes* - let's use
// this to our advantage:
IObservable<string> result_or_loading = Observable.Merge(
DoubleTheString.Select(x => "Loading..."),
doubled_strings
);
Here, one of the computed values is "Loading..." and this value is computed based on the invocation of an ICommand not on some other property value.
What would be very nice is if WPF could handle field level property change notifications as IObservable<T> instead of an event.
private BehaviorSubject<string> firstName = new BehaviorSubject<string>(string.Empty);
public IObservable<string> FirstNameChanged
{
get { return this.firstName.AsObservable(); }
}
public string FirstName
{
get { return this.firstName.FirstOrDefault(); }
set { this.firstName.OnNext(value); }
}
This certainly doesn't make the code any nicer. There's still a lot of boilerplate repetition involved. However things are now really Rx friendly and you can do some pretty powerful composing of events.
--
Quidquid latine dictum sit, altum sonatur.
- Whatever is said in Latin sounds profound.
War is peace. Freedom is slavery. Bugs are features.