Click here to Skip to main content
Click here to Skip to main content

INotifyPropertyChanged auto wiring or how to get rid of redundant code

, 10 Aug 2009
Rate this:
Please Sign up or sign in to vote.
For the last week most of 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 interest

For the last week most of 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 looks like this:

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

while be fully noticeable about any change in any property and makes me able to bind to those properties.

<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 to replace my code with following?:

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 will be used for marking classes requires 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

[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

[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 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 it entry and on it exit for check.

[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 almost done, all we have to do is to create class which implements INotifyPropertyChanged with internal method to useful call

[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 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)

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

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

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

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

Source code for this article (1,225 KB)>>

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)

About the Author

Tamir Khason
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 1 Pinmemberforcemdt6-Aug-12 10:46 
GeneralMy vote of 3 PinmemberNarf the Mouse22-Feb-12 16:28 
Probably very useful, but could not read code.
GeneralMy vote of 5 PinmemberAsher Barak23-Sep-09 21:09 
GeneralRe: My vote of 5 PinmemberAsher Barak23-Sep-09 21:13 
GeneralPerformance Tweak and minor bug fix PinmemberBrian Chance18-Aug-09 11:09 

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

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

| Advertise | Privacy | Mobile
Web01 | 2.8.140721.1 | Last Updated 10 Aug 2009
Article Copyright 2009 by Tamir Khason
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid