Here is the challenge.
- Create a portable library that could be used by almost anything including Console Apps, Windows Phone, ASP.NET, Xbox 360
- Make it data binding friendly for targets like WPF, Silverlight and Windows 8 XAML Metro Apps by supporting property and collection changed notifications.
- Make it efficient so that users who don’t need data binding are not penalized with a performance hit and having to reference libraries they don’t need.
- Write Unit Tests to demonstrate that the solution works for both data binding and non-data binding users.
I was a little stumped so I decided to ask about this on StackOverflow see C# Class library collections ObservableCollection T vs Collection T and based on what the community came up with, here is my solution – can you do better?
The Base Class: Foo
If you don’t want data binding, Foo is for you. It is a simple class. It has a property “Num” and a collection “Strings” with a method called Populate(). You’ll notice that I created some virtual members to allow the derived class to do what it needs to do.
public class Foo
{
private readonly Collection<string> strings = new Collection<string>();
/// <summary>
/// Gets the Num value
/// </summary>
/// <remarks>
/// This property is virtual so that the derived Observable class can provide property changed notifications
/// </remarks>
public virtual int Num { get; set; }
/// <summary>
/// Gets the collection of strings
/// </summary>
public ReadOnlyCollection<string> Strings
{
get
{
return new ReadOnlyCollection<string>(this.strings);
}
}
/// <summary>
/// The collection of strings
/// </summary>
/// <remarks>
/// This property is protected virtual so that the Observable class can substitue an
/// ObservableCollection
/// </remarks>
protected virtual ICollection<string> StringsCollection
{
get
{
return this.strings;
}
}
/// <summary>
/// Populates the string collection
/// </summary>
/// <param name="max">The maximum size for the collection</param>
public void Populate(int max = 1000)
{
// If no num was specified, create a random value
if (Num == 0)
{
var r = new Random();
this.Num = r.Next(1, max);
}
if (Num > max)
{
throw new InvalidOperationException("Num is greater than max");
}
for (var i = 0; i < this.Num; i++)
{
this.StringsCollection.Add(string.Format("string {0}", i));
}
}
}
The Derived Class: ObservableFoo
ObservableFoo is just like Foo except that it is data binding ready because it supports INotifyPropertyChanged and uses an ObservableCollection<string> to do it’s work. The first thing ObservableFoo must do is to get the base class Foo to use an ObservableCollection<string> so it overrides the StringCollection property.
Second, Foo.Strings is a ReadOnlyCollection<string> which won’t do for data binding. Instead we need to return a data binding friendly ReadOnlyObservableCollection<string>. To do this I hide the Foo.Strings property with the new modifier.
Third, I override the Num property so I can fire a PropertyChanged notification when it is set.
Finally, I place ObservableFoo into a different assembly named FooLibrary.Observable since this project must reference System.Windows and forces all those who use it to do the same.
public sealed class ObservableFoo : Foo, INotifyPropertyChanged
{
private readonly ObservableCollection<string> strings = new ObservableCollection<string>();
public event PropertyChangedEventHandler PropertyChanged;
public override int Num
{
get
{
return base.Num;
}
set
{
base.Num = value;
this.OnPropertyChanged("Num");
}
}
public new ReadOnlyObservableCollection<string> Strings
{
get
{
return new ReadOnlyObservableCollection<string>(this.strings);
}
}
protected override ICollection<string> StringsCollection
{
get
{
return this.strings;
}
}
private void OnPropertyChanged(string propertyName)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The Unit Tests
Testing Foo is pretty straight forward so I won't go into detail about that here, but testing ObservableFoo was quite interesting. I have found that in most projects with Model / View / View Model (MVVM) that people neglect to test property change notifications. Then you get subtle bugs in the UI because the property change notifications are not firing as expected. I wanted to create a test that would verify that ObservableFoo was indeed observable so I created the FooObserver. First a test that uses it.
/// <summary>
/// Given
/// * An ObservableFoo
/// When
/// * Populated
/// Then
/// * The number of CollectionChangedNotifications is equal to Num
/// * The number of PropertyChangedNotifications is equal to Num * 2
/// </summary>
[TestMethod]
public void ObservableFooRaisesChangeNotificationsWhenPopulated()
{
// Arrange
const int Expected = 100;
const int ExpectedPropNotifications = Expected * 2;
var foo = new ObservableFoo() { Num = Expected };
// Create an observer to monitor the notifications
var observer = new FooObserver(foo);
// Act
foo.Populate();
// Assert
// Each item that was added to the collection should trigger a CollectionChangedNotification
Assert.AreEqual(Expected, observer.CollectionChangedNotifications.Count);
// Each time an item is added 2 property changed notifications are raised
// Count and Item[]
Assert.AreEqual(ExpectedPropNotifications, observer.PropertyChangedNotifications.Count);
}
Here you can see that my observer simply attaches itself to the ObservableFoo and captures the notifications received so I can assert them. The implementation is not too difficult but interesting.
public class FooObserver
{
private readonly Collection<NotifyCollectionChangedEventArgs> collectionChangedNotifications =
new Collection<NotifyCollectionChangedEventArgs>();
private readonly Collection<PropertyChangedEventArgs> propertyChangedNotifications =
new Collection<PropertyChangedEventArgs>();
public FooObserver(ObservableFoo foo)
{
((INotifyCollectionChanged)foo.Strings).CollectionChanged += this.ObservableStringsOnCollectionChanged;
((INotifyPropertyChanged)foo.Strings).PropertyChanged += this.OnPropertyChanged;
foo.PropertyChanged += this.OnPropertyChanged;
}
public ReadOnlyCollection<NotifyCollectionChangedEventArgs> CollectionChangedNotifications
{
get
{
return new ReadOnlyCollection<NotifyCollectionChangedEventArgs>(this.collectionChangedNotifications);
}
}
public ReadOnlyCollection<PropertyChangedEventArgs> PropertyChangedNotifications
{
get
{
return new ReadOnlyCollection<PropertyChangedEventArgs>(this.propertyChangedNotifications);
}
}
private void ObservableStringsOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
{
this.collectionChangedNotifications.Add(args);
}
private void OnPropertyChanged(object sender, PropertyChangedEventArgs args)
{
this.propertyChangedNotifications.Add(args);
}
}
What Now?
Frankly, I hate to ship another assembly, thankfully NuGet will make this less painful. If this works as well as it appears to, I suppose I will start shipping data binding friendly versions of some of the types found in Microsoft.Activities.Extensions. Am I on the right track? After all, I could be missing something obvious. I could just make the base classes data binding friendly and make everyone reference System.Windows. The performance penalty is not that great, but then when there is a good solution why not use it?
Happy Coding!
Ron Jacobs
blog: http://blogs.msdn.com/rjacobs
Twitter: @ronljacobs