Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why would overwriting .GetHashCode clear these databound values in WinForms?

We have run into a strange bug that we're having problems debugging.

We have a MDI workspace that uses Microsoft CAB, DevExpress components, and .Net 3.5.

If users open two windows in the workspace that each contain a UserControl bound to two separate data models, then minimize both of them, the first window to minimize is getting it's bound fields cleared when the second one minimizes.

The .Equals and .GetHashCode methods of the data model have been overridden so that both data models are considered equal. If we change that so they are unique, we do not get this behavior.

Here's some example pseudocode showing the problem

var a = new MyWindow();
a.DataModel = new SomeClass(123);
a.ShowInMdiWorkspace();

var b = new MyWindow();
b.DataModel = new SomeClass(123);
b.ShowInMdiWorksace();

a.Minimize();

// If SomeClass.GetHashCode() is overwritten to consider two objects  
// as equal based on the value passed in, then the data bindings for A
// get cleared on this call. If SomeClass.GetHashCode is unique, then 
// this problem does not happen.
b.Minimize();

Here's the Call Stack when the second window gets minimized:

enter image description here

At the EndEditSession() call in the stack trace above, it is calling EndEditSession for the second window minimized, while by the time the Stack Trace gets past the [External Code] to the OnChange breakpoint I have set, it is firing the change method in the first window.

EndEditSession() is something custom we have implemented which looks something like this

protected void EndEditSession()
{
    IBindingValue bv = null;

    if (_bindingValues == null)
        return;

    if (_data != null)
    {
        foreach (KeyValuePair<string, IBindingValue> kvp in _bindingValues)
        {
            bv = kvp.Value;
            if (bv.IsBindable)
                ((PropertyManager)bv.Component.BindingContext[_data]).EndCurrentEdit();
        }
    }

}

_bindingValues gets populated when the UserControl initializes its data bindings. The key fields are the name of the bound control, and the value fields are a custom object which stores the control itself, its name, its bound value, and default value. bv.Component returns the control that the binding is set on, which in the case of my testing is a customized DevExpress LookupEdit

_data contains the data model for the UserControl, and I can verify that it is set to the instance for the second window.

My original thought was that the BindingContext was shared so the wrong PropertyManager was being returned, however I have verified that the .BindingContext for the two forms and controls are separate.

Is it possible that having two separate copies of a UserControl bound to two separate instances of a data model would get its bindings mixed up when the GetHashCode method has been overridden so that the two objects are considered equal?

I am not very familiar with the inner workings of the WinForms binding system, or with exactly how CAB's MDI workspace gets managed.

My theory is that when the first window minimizes, it is unloading the controls to save on memory, then when the second window minimizes the internal hash table that manages the bindings is incorrectly getting confused and running an update to take data from the first minimized window (which is now blank) and updating its datasource. There are plenty of holes in this theory, however its the only thing I can think of.

like image 238
Rachel Avatar asked Jun 11 '14 18:06

Rachel


1 Answers

I don't know the internal workings the WinForm widget, but it seems that since you've encountered an issue with overriding equals that you'd be better off working around.

If you need to evaluate equality for your own purposes:

An approach is to provide your own method to evaluate equality, rather than changing the default behavior.

If your intention is to change how the widget treats the objects:

An approach is to make a static object factory for your class. The factory could maintain a collection of all of the objects created using weak references. Weak references allow the GC to collect the objects. The factory can then check the collection of previously created objects. If a match is found then return the existing one. If not then create it. This way rather than having two different objects that evaluate two equal (override equals) you'd have a single object with two references that is equal (same memory).

Hopefully one of these other approaches will solve your problem.

like image 102
N-ate Avatar answered Sep 24 '22 16:09

N-ate