Click here to Skip to main content
15,884,473 members
Articles / Desktop Programming / WPF

INotifyPropertyChanged Auto Wiring or How to Get Rid of Redundant Code

Rate me:
Please Sign up or sign in to vote.
4.48/5 (11 votes)
10 Aug 2009Ms-PL2 min read 51K   13   8
INotifyPropertyChanged auto wiring or how to get rid of redundant code

For the last week, most WPF disciples are discussing how to get rid of hardcoded property name string inside INotifyPropertyChanged implementation and how to keep using automatic properties implementation but keep WPF binding working. The thread was started by Karl Shifflett, who proposed interesting method of using StackFrame for this task. During this thread, other methods were proposed including code snippets, R#, Observer Pattern, Cinch framework, Static Reflection, Weak References and others. I also proposed the method we’re using for our classes and promised to blog about it. So the topic today is how to use PostSharp to wire automatic implementation of INotifyPropertyChanged interface based on automatic setters only.

My 5 ¢

So, I want my code to look like this:

C#
public class AutoWiredSource { 
   public double MyProperty { get; set; } 
   public double MyOtherProperty { get; set; } 
}

while being fully noticeable about any change in any property and enables me to bind to those properties.

XML
<StackPanel DataContext="{Binding Source={StaticResource source}}"> 
    <Slider Value="{Binding Path=MyProperty}" /> 
    <Slider Value="{Binding Path=MyProperty}" /> 
</StackPanel>

How to achieve it? How to make compiler replace my code with the following?

C#
private double _MyProperty; 
public double MyProperty { 
   get { return _MyProperty; } 
   set { 
      if (value != _MyProperty) { 
         _MyProperty = value; OnPropertyChanged("MyProperty"); 
      } 
   } 
} 
public event PropertyChangedEventHandler PropertyChanged; 
internal void OnPropertyChanged(string propertyName) { 
   if (string.IsNullOrEmpty(propertyName)) throw new ArgumentNullException(
       "propertyName"); 

var handler = PropertyChanged as PropertyChangedEventHandler; 
   if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); 
}

Simple: to use aspect oriented programming to inject set of instructions into pre-compiled source.

First of all, we have to build some attribute which will be used for marking classes that require change tracking. This attribute should be combined (compound) aspect to include all aspects used for change tracking. All we’re doing here is to get all set methods to add composition aspect to:

C#
[Serializable, DebuggerNonUserCode, AttributeUsage(
    AttributeTargets.Assembly | AttributeTargets.Class, AllowMultiple = false,
    Inherited = false), 
MulticastAttributeUsage(MulticastTargets.Class, AllowMultiple = false,
    Inheritance = MulticastInheritance.None, AllowExternalAssemblies = true)] 
public sealed class NotifyPropertyChangedAttribute : CompoundAspect { 
   public int AspectPriority { get; set; } 
   public override void ProvideAspects(object element,
      LaosReflectionAspectCollection collection) { 
      Type targetType = (Type)element; 
      collection.AddAspect(targetType, new PropertyChangedAspect { 
          AspectPriority = AspectPriority }); 
      foreach (var info in targetType.GetProperties(
         BindingFlags.Public | BindingFlags.Instance).Where(
            pi => pi.GetSetMethod() != null)) { 
         collection.AddAspect(info.GetSetMethod(), new NotifyPropertyChangedAspect(
             info.Name) { AspectPriority = AspectPriority }); 
      } 
   } 
}

Next aspect is change tracking composition aspect. Which is used for marking only:

C#
[Serializable] 
internal sealed class PropertyChangedAspect : CompositionAspect { 
   public override object CreateImplementationObject(
      InstanceBoundLaosEventArgs eventArgs) { 
      return new PropertyChangedImpl(eventArgs.Instance); 
   } 
public override Type GetPublicInterface(Type containerType) { 
      return typeof(INotifyPropertyChanged); 
   }
public override CompositionAspectOptions GetOptions() { 
      return CompositionAspectOptions.GenerateImplementationAccessor; 
   } 
}

And the next which is the most interesting one, we will put onto method boundary for tracking. There are some highlights here. First, we do not want to fire PropertyChanged event if the actual value did not changed, thus we’ll handle the method on its entry and on its exit for check.

C#
[Serializable] 
internal sealed class NotifyPropertyChangedAspect : OnMethodBoundaryAspect { 
   private readonly string _propertyName; 
public NotifyPropertyChangedAspect(string propertyName) { 
      if (string.IsNullOrEmpty(propertyName)) throw new ArgumentNullException(
          "propertyName"); 
      _propertyName = propertyName; 
   } 
public override void OnEntry(MethodExecutionEventArgs eventArgs) { 
      var targetType = eventArgs.Instance.GetType(); 
      var setSetMethod = targetType.GetProperty(_propertyName); 
      if (setSetMethod == null) throw new AccessViolationException(); 
      var oldValue = setSetMethod.GetValue(eventArgs.Instance,null); 
      var newValue = eventArgs.GetReadOnlyArgumentArray()[0]; 
      if (oldValue == newValue) eventArgs.FlowBehavior = FlowBehavior.Return; 
   } 
public override void OnSuccess(MethodExecutionEventArgs eventArgs) { 
      var instance = eventArgs.Instance as IComposed<INotifyPropertyChanged>; 
      var imp = instance.GetImplementation(
          eventArgs.InstanceCredentials) as PropertyChangedImpl; 
      imp.OnPropertyChanged(_propertyName); 
   } 
}

We're almost done, all we have to do is to create a class which implements INotifyPropertyChanged with internal method to useful call:

C#
[Serializable] 
internal sealed class PropertyChangedImpl : INotifyPropertyChanged { 
   private readonly object _instance; 
public PropertyChangedImpl(object instance) { 
      if (instance == null) throw new ArgumentNullException("instance"); 
      _instance = instance; 
   }
public event PropertyChangedEventHandler PropertyChanged;
internal void OnPropertyChanged(string propertyName) { 
      if (string.IsNullOrEmpty(propertyName)) throw new ArgumentNullException(
          "propertyName"); 

var handler = PropertyChanged as PropertyChangedEventHandler; 
      if (handler != null) handler(_instance, new PropertyChangedEventArgs(propertyName)); 
   } 
}

We're done. The last thing is to reference to PostSharp Laos and Public assemblies and mark compiler to use Postsharp targets (inside your project file (*.csproj)

XML
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> 
<PropertyGroup> 
  <DontImportPostSharp>True</DontImportPostSharp> 
</PropertyGroup> 
<Import Project="PostSharp\PostSharp-1.5.targets" />

Now we're done. We can use clear syntax like the following to make all our properties having public setter to be traceable. The only disadvantage is that you’ll have to drag two PostSharp files with your project. But after all, it is much more convenient than manual notify change tracking all over your project.

C#
[NotifyPropertyChanged] 
public class AutoWiredSource { 
   public double MyProperty { get; set; } 
}

Have a nice day and be good people. Also try to think what other extremely useful things can be done with PostSharp (or any other aspect oriented engine).

Related Posts

  1. Nifty time savers for WPF development
  2. Set binding, based on trigger
  3. Auto scroll ListBox in WPF

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)


Written By
Architect Better Place
Israel Israel
Hello! My name is Tamir Khason, and I am software architect, project manager, system analyst and [of course] programmer. In addition to writing big amount of documentation, I also write code, a lot of code. I used to work as a freelance architect, project manager, trainer, and consultant here, in Israel, but recently join the company with extremely persuasive idea - to make a world better place. I have very pretty wife and 3 charming kids, but unfortunately almost no time for them.

To be updated within articles, I publishing, visit my blog or subscribe RSS feed. Also you can follow me on Twitter to be up to date about my everyday life.

Comments and Discussions

 
GeneralMy vote of 5 Pin
Asher Barak23-Sep-09 21:09
professionalAsher Barak23-Sep-09 21:09 
GeneralRe: My vote of 5 Pin
Asher Barak23-Sep-09 21:13
professionalAsher Barak23-Sep-09 21:13 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.