Introduction
This article is about implementing the INotifyPropertyChanged interface automatically with a new interface ICanBeDirty to detect if the model has changes using a custom generated proxy. Typically, INotifyPropertyChanged and change detecting are useful in WPF binding scenarios. There is an article from Simon Cropp describing many possible solutions to the problem; Simon himself proposes a solution injecting INotifyPropertyChanged code into properties at compile time.
This solution is based on the excellent work done by Einar Ingebrigtsen, he proposed a ProxyGenerator
to transform this...
public class Foo
{
public virtual string Name1 { get; set; }
public string Name2
{
get
{
return Name1 + Date1.ToString();
}
}
public virtual bool Boolean1 { get; set; }
public virtual DateTime Date1 { get; set; }
public virtual object Value1 { get; set; }
}
...into something like this...
public class FooDerived : Foo, INotifyPropertyChanged, ICanBeDirty
{
private IDispatcher _dispatcher = DispatcherManager.Current;
public FooDerived(Foo source)
{
Date1 = source.Date1;
Name1 = source.Name1;
Boolean1 = source.Boolean1;
Value1 = source.Value1;
}
public override string Name1
{
get { return base.Name1; }
set
{
if (!Equals(base.Name1, value))
{
base.Name1 = value;
OnPropertyChanged("Name1");
OnPropertyChanged("Name2");
}
}
}
public override object Value1
{
get { return base.Value1; }
set
{
if (!Equals(base.Value1, value))
{
base.Value1 = value;
OnPropertyChanged("Value1");
}
}
}
public override bool Boolean1
{
get { return base.Boolean1; }
set
{
if (base.Boolean1 != value)
{
base.Boolean1 = value;
OnPropertyChanged("Boolean1");
}
}
}
public override DateTime Date1
{
get { return base.Date1; }
set
{
if (base.Date1 != value)
{
base.Date1 = value;
OnPropertyChanged("Date1");
OnPropertyChanged("Name2");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
SetDirty();
OnPropertyChangedInner(name);
}
protected void OnPropertyChangedInner(string name)
{
if (null != PropertyChanged)
{
if (_dispatcher.CheckAccess())
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
else
{
_dispatcher.BeginInvoke
(PropertyChanged, this, new PropertyChangedEventArgs(name));
}
}
}
private bool isDirty;
public bool IsDirty
{
get
{
return isDirty;
}
}
public void Save()
{
if (isDirty)
{
isDirty = false;
OnPropertyChangedInner("IsDirty");
}
}
private void SetDirty()
{
if (!isDirty)
{
isDirty = true;
OnPropertyChangedInner("IsDirty");
}
}
}
Why to extend the Einar's solution? Mainly because in a WPF binding scenario, developers often need to have some kind of change verification; this solution extends the previous one adding "dirty checking" implementing the ICanBeDirty interface, a copy constructor and automatic PropertyChanged
events for related properties, that means the developer does not need to setup manually the dependences in a property. This solution has an optimization to check if the new value is different from the old one before raising the event. This solution is intended for WPF binding, in consequence the performance lost to instantiate the proxy is not important, in the Points of Interest section, some performance tests results will be presented.
Using the Code
This solution lets the developer write this kind of code:
private static void ProxyWeaverTest()
{
DispatcherManager.Current = new Dispatcher
(System.Windows.Threading.Dispatcher.CurrentDispatcher);
var weaver = new NotifyingObjectWeaver();
var myFoo = new Foo()
{
Boolean1 = true,
Value1 = new { LittleFoo = 2 },
Date1 = DateTime.Now,
Name1 = "Name1"
};
Foo myFooCopy = weaver.CreateInstance(myFoo);
var fooCanBeDirty = myFooCopy as ICanBeDirty;
System.Console.WriteLine(string.Format
("myFooCopy.Boolean1 = {0}", myFooCopy.Boolean1));
System.Console.WriteLine(string.Format
("myFooCopy.Value1 = {0}", myFooCopy.Value1));
System.Console.WriteLine(string.Format
("myFooCopy.Name1 = {0}", myFooCopy.Name1));
System.Console.WriteLine(string.Format
("myFooCopy.Name2 = {0}", myFooCopy.Name2));
System.Console.WriteLine(string.Format
("myFooCopy.Date1 = {0}", myFooCopy.Date1));
System.Console.WriteLine(string.Format
("myFooCopy.IsDirty after copy = {0}", fooCanBeDirty.IsDirty));
System.Console.WriteLine("Subscribing to PropertyChanged event ...");
(myFooCopy as INotifyPropertyChanged).PropertyChanged +=
(s, e) => System.Console.WriteLine(string.Format
("Changed :{0}", e.PropertyName));
System.Console.WriteLine(string.Format
("Assigning {0} to {1} ...", "myFooCopy.Name1", "New Name1"));
myFooCopy.Name1 = "New Name1";
System.Console.WriteLine(string.Format
("myFooCopy.IsDirty after changes = {0}", fooCanBeDirty.IsDirty));
var newDate = DateTime.Now.AddDays(1);
System.Console.WriteLine(string.Format
("Assigning {0} to {1} ...", "myFooCopy.Date1", newDate));
myFooCopy.Date1 = newDate;
System.Console.WriteLine("Saving myFooCopy ...");
fooCanBeDirty.Save();
System.Console.WriteLine(string.Format
("myFooCopy.IsDirty after saving myFooCopy = {0}", fooCanBeDirty.IsDirty));
System.Console.WriteLine("Press any key to exit ...");
System.Console.ReadKey();
}
Note at first the set-up for the DispatcherManager
in order to make the proxy "Dispatcher
" friendly, for more details, see this Einar's post.
The main class in the solution is the NotifyingObjectWeaver
class, this class supports two generics methods to get a proxy instance from a base class:
public T CreateInstance<T>()
public T CreateInstance<T>(T source)
The proxy type can be obtained with:
public Type GetProxyType<T>()
or:
public Type GetProxyType(Type baseType)
in order to set-up an IoC container.
The proxy weaver can build a new instance or a copy based instance, in this case the test gets a copy based instance:
Foo myFooCopy = weaver.CreateInstance(myFoo);
In order to subscribe to change notification, it is necessary to get the INotifyPropertyChanged interface:
(myFooCopy as INotifyPropertyChanged).PropertyChanged +=
(s, e) => System.Console.WriteLine(string.Format("Changed :{0}", e.PropertyName));
It is necessary to get a reference to the ICanBeDirty
interface in order to access the IsDirty
flag and the Save()
method, this interface is simple:
public interface ICanBeDirty
{
bool IsDirty { get; }
void Save();
}
It is not necessary to use attributes to mark dependencies, by example, this property in Foo
...
public string Name2
{
get
{
return Name1 + Date1.ToString();
}
}
...generates this output in the proxy generated class:
public override string Name1
{
get { return base.Name1; }
set
{
if (!Equals(base.Name1, value))
{
base.Name1 = value;
OnPropertyChanged("Name1");
OnPropertyChanged("Name2");
}
}
}
public override DateTime Date1
{
get { return base.Date1; }
set
{
if (base.Date1 != value)
{
base.Date1 = value;
OnPropertyChanged("Date1");
OnPropertyChanged("Name2");
}
}
}
Because the proxy generator analyses the source code to find dependencies automatically.
In the original Einar's solution, dependencies are managed by the NotifyChangesForAttribute
adding this attribute to the base class:
[NotifyChangesFor("Boolean1", "Name2")]
public virtual object Value1 { get; set; }
This attribute lets raise changes for Boolean1
and Name2
when Value1
is changed. This solution does not support this attribute.
There is another attribute IgnoreChangesAttribute
for not raising change events when a property is changed.
The rest of the code is straightforward; running this demo, the output is:
Points of Interest
The NotifyingObjectWeaver
class imposes some restrictions to the base class:
- The base class must have a default constructor in order to implement a copy constructor
- Only the members having a virtual
public
set can raise change notifications
The test method ProxyWeaverPerformanceTest
generates these results:
Number of instances |
Time with new (msc) |
Time with Proxy weaver (msc) |
1000 |
0 |
0 |
10000 |
0 |
0 |
100000 |
31 |
62 |
1000000 |
281 |
546 |
10000000 |
3078 |
5437 |
In the normal case, by example binding a result list with a datagrid
, the grid can display at most 10 to 100 items, which means the performance lost is not really an issue in this case.
Another interesting feature to explain is the automatic change notification for related properties, Simon Crop has implemented an excellent solution using Mono.Cecil
, and in this solution the "analysis engine" was built using the IL reader code in the Acid Framework.
The code for the proxy weaver, including the code analyzer to find related properties is complex, the source is attached here, suggestions and comments are very welcome!!!
What is Next?
There are a lot of things still to do, at first, in the next post a demo with caliburn micro in WPF will show how to integrate the proxy weaver with the view model and the ProxyWrapper
class, but some new features are planned for the weaver:
- A fluent configuration to support ignore changes for property and choose the supported interfaces
- Implement an extension for
INotifyPropertyChanges
to raise changes for all properties, get before/after property values and turn on/off the notification
- Implement
IEditableObject
- A refactoring to use a new code Emitter
- Localize the error messages (French)
- Solve the problem of nested classes and databinding
- Integrate more unit tests