Using Aspect Oriented Programming to implement INotifyPropertyChanged as an attribute
Using Aspect Oriented Programming, specifically PostSharp, they have implemented INotifyPropertyChanged in two ways:
- A simple single INotifyPropertyChanged aspect as described here: http://www.sharpcrafters.com/solutions/notifypropertychanged
- The Definitive INotifyPropertyChanged which can be found here: http://www.sharpcrafters.com/blog/category/Toolkits.aspx
I recommend you look at both of those methods and see if they meet your needs. Of they do use them.
I didn’t find either implementation to my liking. The first is too simple, and the second is too complex. Both seem to think that I am going to want all my properties to kick off the OnPropertyChanged event. They both implement the class as follows.
[NotifyPropertyChanged] public class Person { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } }
All three properties will implement INotifyPropertyChanged even though it may be that Id never changes or if it does change, there is no need to notify anything.
NotifyPropertyChanged Attribute Expectations
Here is how I want to use Aspect Oriented Programming in my MVVM objects.
// This adds the interface and the OnNotifyPropertyChanged method. [NotifyPropertyChangedClass] public class Person { // Does NOT call OnNotifyPropertyChanged public int Id { get; set; } // Calls OnNotifyPropertyChanged [NotifyPropertyChanged] public string FirstName { get; set; } // Calls OnNotifyPropertyChanged [NotifyPropertyChanged] public string LastName { get; set; } }
Now, I also want to be able to specify the notifying of a property changed:
[NotifyPropertyChangedClass] public class Person { // Does NOT call OnNotifyPropertyChanged public int Id { get; set; } // Calls OnNotifyPropertyChanged for the property and the string params passed in. [NotifyPropertyChanged("LastCommaFirst", "FirstLast")] public string FirstName { get; set; } // Calls OnNotifyPropertyChanged [NotifyPropertyChanged] public string LastName { get; set; } public string LastCommaFirst { get { return string.Format("{1}, {0}", FirstName, LastName); } } public string FirstLast { get { return string.Format("{1}, {0}", FirstName, LastName); } } }
Aspect MVVM
I was able to implement this with three very simple files: 1 interface, and two aspects. Here is how I implemented this:
The first file, INotifyPropertyChangedWithMethod.cs, is an interface that inherits from INotifyPropertyChanged. I used this to get access to the OnPropertyChanged event method.
using System.ComponentModel; namespace MVVM { public interface INotifyPropertyChangedWithMethod : INotifyPropertyChanged { void OnPropertyChanged(string propertyName); } }
The second object is basically the same as the simple single INotifyPropertyChanged aspect, except it only implements the interface and does nothing to the setters.
using System; using System.ComponentModel; using PostSharp.Aspects; using PostSharp.Aspects.Advices; using PostSharp.Extensibility; using PostSharp.Reflection; namespace MVVM { [Serializable] [IntroduceInterface(typeof(INotifyPropertyChangedWithMethod), OverrideAction = InterfaceOverrideAction.Ignore)] [MulticastAttributeUsage(MulticastTargets.Class, Inheritance = MulticastInheritance.Strict)] public sealed class NotifyPropertyChangedClassAttribute : InstanceLevelAspect, INotifyPropertyChangedWithMethod { [ImportMember("OnPropertyChanged", IsRequired = false, Order = ImportMemberOrder.AfterIntroductions)] public Action OnPropertyChangedMethod; [IntroduceMember(Visibility = Visibility.Family, IsVirtual = true, OverrideAction = MemberOverrideAction.Ignore)] public void OnPropertyChanged(string propertyName) { if (this.PropertyChanged != null) { PropertyChanged(Instance, new PropertyChangedEventArgs(propertyName)); } } [IntroduceMember(OverrideAction = MemberOverrideAction.Ignore)] public event PropertyChangedEventHandler PropertyChanged; } }
The final aspect is one that I use on the properties.
using System; using PostSharp.Aspects; namespace MVVM { [Serializable] public class NotifyPropertyChanged : LocationInterceptionAspect { public NotifyPropertyChanged() : this(true, null) { } public NotifyPropertyChanged(params string[] inProperty) : this(true, inProperty) { } public NotifyPropertyChanged(bool inNotifyCurrentProperty, params string[] inProperty) { _NotifyCurrentProperty = inNotifyCurrentProperty; _OtherProperties = inProperty; } private readonly string[] _OtherProperties; private readonly bool _NotifyCurrentProperty; public override void OnSetValue(LocationInterceptionArgs args) { // Do nothing if the property value doesn't change if (args.Value == args.GetCurrentValue()) return; args.ProceedSetValue(); var npc = args.Instance as INotifyPropertyChangedWithMethod; if (npc != null) { // Notify for current property if (_NotifyCurrentProperty) npc.OnPropertyChanged(args.Location.PropertyInfo.Name); // Notify for other properties if (_OtherProperties != null) { foreach (string otherProperty in _OtherProperties) { npc.OnPropertyChanged(otherProperty); } } } } } }
My MVVM objects still include RelayCommand, but they no longer need ObservableObject or ViewModelBase.
Differences Encountered using Aspect MVVM
Calling OnPropertyChanged or Adding to the PropertyChanged event
First, since the classes themselves don’t actually have code for INotifyPropertyChanged or the OnPropertyChanged method, what do you do if you need to actually call OnPropertyChanged in one of the classes methods instead of in a property? What if you need to add an method to the PropertyChanged event? Visual Studio is going to give compile errors? Both are easily doable using the interface.
private void SomeMethod(object sender) { var npc = this as INotifyPropertyChangedWithMethod; if (npc != null) npc.OnPropertyChanged("Sentences"); }
private void Instance_SearchWordsChangedEvent(object sender) { var npc = MyObject as INotifyPropertyChangedWithMethod; if (npc != null) npc.PropertyChanged += NpcOnPropertyChanged; } private void NpcOnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs) { // code here... }
Download
Here is a sample project and a project template and also a separate AspectMVVM project if you want the code. I’ll open source it on CodePlex.com soon.
AspectMVVM Example Project.zip
AspectMVVM Project Template.zip
AspectMVVM.zip