INotifyPropertyChanged with custom targets

Wouldn’t it be nice to have a simple attribute instead of backing field madness in Silverlight/WPF? Just like this:

public class MainViewModel : ViewModelBase
    public string Title { get; set; }

You can use PostSharp for that, you should at least use lambda expressions instead of strings.

Here’s how to do it without 3rd party software:

In your project declare base view model:

public class ViewModelBase : INotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged
        = delegate { };

    protected void OnPropertyChanged(string propertyName)
            new PropertyChangedEventArgs(propertyName));

Declare the attribute

It will be used to mark properties that should raise the PropertyChanged event.

public class NotifyPropertyChangedAttribute : Attribute

Create new MSBuildTasks library project (it can be in different solution)

Add references to:

  • Mono.Cecil.dll
  • Mono.Cecil.Pdb.dll (this needed so Cecil can updated pdb file, which is need for debugging the modified assembly)
  • MSBuild assemblies

Create new MSBuild task

It will inject PropertyChanged event invocation on properties marked with NotifyPropertyChanged attribute.

using System;
using System.IO;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Mono.Cecil;
using Mono.Cecil.Cil;

namespace MSBuildTasks
    public class NotifyPropertyChangedTask : Task
        public override bool Execute()
            return true;

        private void InjectMsil()
            var assemblyDefinition = AssemblyDefinition.ReadAssembly(
                new ReaderParameters { ReadSymbols = true });

            var module = assemblyDefinition.MainModule;

            foreach (var type in module.Types)
                foreach (var prop in type.Properties)
                    foreach (var attribute in prop.CustomAttributes)
                        string fullName = attribute.Constructor.DeclaringType.FullName;
                        if (fullName.Contains("NotifyPropertyChanged"))
                            InjectMsilInner(module, type, prop);
                new WriterParameters { WriteSymbols = true });

        private static void InjectMsilInner(
            ModuleDefinition module,
            TypeDefinition type,
            PropertyDefinition prop)
            var msilWorker = prop.SetMethod.Body.GetILProcessor();
            var ldarg0 = msilWorker.Create(OpCodes.Ldarg_0);

            MethodDefinition raisePropertyChangedMethod =
            if (raisePropertyChangedMethod == null)
                throw new Exception(
                    "RaisePropertyChanged method was not found in type "
                    + type.FullName);

            var raisePropertyChanged = module.Import(
            var propertyName = msilWorker.Create(
            var callRaisePropertyChanged = msilWorker.Create(

                    prop.SetMethod.Body.Instructions.Count - 1],

            msilWorker.InsertAfter(ldarg0, propertyName);

        private static MethodDefinition FindRaisePropertyChangedMethod(
            TypeDefinition type)
            foreach (var method in type.Methods)
                if (method.Name == "RaisePropertyChanged"
                    &&  method.Parameters.Count == 1
                    &&  method.Parameters[0].ParameterType.FullName
                        == "System.String")
                    return method;
            if (type.BaseType.FullName == "System.Object")
                return null;
            return FindRaisePropertyChangedMethod(

        public string AssemblyPath { get; set; }

Compile the task assembly,

and copy it to “$(SolutionDir)/../lib/MSBuild/MSBuildTasks.dll” folder along with Mono.Cecil.dll and Mono.Cecil.Pdb.dll assemblies.

Finally modify your Silverlight/WPF project (.csproj file):

  <usingTask TaskName="MSBuildTasks.NotifyPropertyChangedTask" AssemblyFile="$(SolutionDir)..libMSBuildMSBuildTasks.dll" />
  <target Name="AfterCompile">
    <msbuildTasks.NotifyPropertyChangedTask AssemblyPath="$(ProjectDir)obj$(Configuration)$(TargetFileName)" />

Voila! Enjoy!

Here’s the source code of the MSBuildTasks project:

Tags: , ,

3 Responses to “INotifyPropertyChanged with custom targets”

  1. Simon Says:

    I started with something similar when I began this

    Here are some pointers
    -“$(ProjectDir)obj$(Configuration)$(TargetFileName)” can be replaced with “@(IntermediateAssembly)”
    -You probably don’t need to insert Nops. The compiler places them in to make it easier for the debugger to set breakpoints. Since u have no source code they are not necessary
    -You have not handled signed assemblies
    -module.Types does not return nested types
    -should only attempt to weave types that are instance classes for perf reasons
    -you don’t skip abstract, static or readonly properties
    -since INPC is for databinding you should ignore properties with no get
    -you don’t skip properties that already call OnNotifyPropertyChanged
    -because you not define a custom IAssemblyResolver you will have problems if you try to weave Silverlight or windows phone 7. The reason is because you are running in a .net 4 context when you call Resolve it may incorrectly resolve to a .net 4 assembly rather than a SL or WP7 assembly
    -I suspect you will have a bug with generic types
    -you assume RaisePropertyChanged is virtual and force a callvirt

    Hope this helps

  2. Limilabs support Says:



    > module.Types does not return nested types
    Don’t even remember when I declared one.

    > – you don’t skip abstract, static or readonly properties
    > – since INPC is for databinding you should ignore properties with no get
    Remember that I have control on which property I apply NotifyPropertyChanged attribute

    > -you don’t skip properties that already call OnNotifyPropertyChanged
    This is on purpose. I have control on which property I apply NotifyPropertyChanged attribute

    > -you assume RaisePropertyChanged is virtual and force a callvirt

  3. Simon Says:

    yeah. sorry. I forgot you were only going the attribute approach.


Consider using our Q&A forum for asking any questions.