Here is the challenge.
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?
Download Sample Code
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)); } }}
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)); } }}
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); }}
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