Star date: 2009.234
As I described in this previous article raising the PropertyChanged
event for classes that implement INotifyPropertyChanged
can be a real pain. The biggest problem is that PropertyChangedEventArgs
takes the name of the property that changed as a string and as we all know, strings are the root of all evil. Here I will show how to use a simple PostSharp attribute on your properties that need to raise the PropertyChanged
event when they are changed so that you don't manually need to do it and hard code the name of the property as a string. PostSharp is a framework for .NET that allows for aspect oriented programming. You can read all about it at the PostSharp website.
First of all, let's assume that the classes implementing INotifyPropertyChanged
are model view classes in the MVVM pattern. We will use a base class for all model views called BaseModelView
that looks like this:
Imports System.ComponentModel
''' <summary>
''' Parent class for all model views
''' </summary>
Public Class BaseModelView
Implements INotifyPropertyChanged
Public Event PropertyChanged( _
ByVal sender As Object, _
ByVal e As System.ComponentModel.PropertyChangedEventArgs) _
Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
''' <summary>
''' Raises the <c>PropertyChanged</c> event for the property with the given name.
''' </summary>
''' <param name="propertyName">The name of the property that has changed.</param>
''' <remarks>If there is no property on this class with the given name, then an
''' exception will be thrown.</remarks>
Public Sub OnPropertyChanged(ByVal propertyName As String)
' Throw an exception if the property doesn't exist
If Me.GetType().GetProperty(propertyName) Is Nothing Then
Throw New ArgumentException( _
String.Format("The property {0} doesn't exist on type {1}.", _
propertyName, _
Me.GetType().Name))
End If
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
End Class
This class is very important. There needs to be a method for property change notification (ie: OnPropertyChanged
on BaseModelView
) instead of just an event (ie: PropertyChanged
on INotifyPropertyChanged
) because the attribute cannot directly raise an event, but it can call a public method that raises the event. The PostSharp attribute looks like this:
Imports PostSharp.Laos
Imports System.ComponentModel
<Serializable()> _
Public Class NotifyAttribute
Inherits OnMethodBoundaryAspect
Public Overrides Sub OnExit(ByVal eventArgs As PostSharp.Laos.MethodExecutionEventArgs)
' Convert to BaseModelView
Dim notifier = TryCast(eventArgs.Instance, BaseModelView)
' If the instance is the wrong type then throw an exception
If notifier Is Nothing Then
Throw New InvalidOperationException("Cannot raise PropertyChanged event unless instance implements INotifyPropertyChanged.")
End If
' Ignore everything that's not a setter
If eventArgs.Method.Name.StartsWith("set_") Then
notifier.OnPropertyChanged(eventArgs.Method.Name.Substring(4))
End If
End Sub
End Class
Note that when you apply PostSharp attribute to a property, you are actually applying the attribute to the two methods that are generated for that property. For example, if you have a property called MyProperty
then the compiler will actually generate two methods: getMyProperty
and setMyProperty
. Since OnExit()
will actually get called for both of these methods when we apply the attribute to a property, the code has to check whether the getter or the setter was called. Using the attribute is very simple:
<Notify()> _
Public Property Text() As String
Get
Return _text
End Get
Set(ByVal value As String)
_text = value
End Set
End Property
The result is that the PropertyChanged event will automatically be raised after the setter finishes executing and there is no need to hard code any strings! Now you are free to change the name of your property and it won't break any code.