To do what you're trying to do, there is probably a much simpler method: Use a "modified" flag or event.
You can cascade this up many levels of user controls if need be. Just declare the event like this:
public class MyControl : Control
{
public MyControl()
{
InitializeComponent();
textBox1.TextChanged += BubbleModified;
// etc.
}
protected void BubbleModified(object sender, EventArgs e)
{
OnModified(e);
}
protected void OnModified(EventArgs e)
{
var handler = Modified;
if (handler != null)
handler(this, e);
}
[Category("Behavior")]
[Description("Occurs when data on the control is modified.")]
public event EventHandler Modified;
}
Then, at whatever level you need to actually check for modifications, hook all of the events.
public class MainForm : Form
{
private bool isDataModified;
public MainForm()
{
InitializeComponent();
textBox1.TextChanged += DataModified;
textBox2.TextChanged += DataModified;
// etc.
userControl1.Modified += DataModified;
userControl2.Modified += DataModified;
// etc.
}
private void DataModified(object sender, EventArgs e)
{
isDataModified = true;
}
}
Then all you have to do is check (and reset) the isDataModified flag accordingly.
It's really not a lot of work, certainly easier than ensuring that INotifyPropertyChanged is implemented for every object in the graph. Remember, this is a form, you don't really care that the object changed, you care if the user made a change, and for that, you want to actually check for changes made through the controls.
Yeah, it's not perfect - you still run into the minor nuisance of reporting that data was changed even when the user changes something and then changes it back. But I don't think I've ever actually heard a complaint about this, and using serialization as a comparison method just isn't reliable. What you really need to do if you want to eliminate the redundant save confirmation is override the Equals method of every object in the graph and implement an actual value-equality operation. Or, if you don't want to retain a copy of the old graph, use a checksum-generating function (collisions are possible but highly unlikely).
But I would just stick with the flag. Don't try to cheat your way out of writing equality checks. It's actually sort of the same as trying to write an automatic deep-cloning method; you can try, but anything you come up with is going to be broken sometimes.