Due to the ugly issue with the way WPF handles equality between objects (which I believe is fundamentally broken, but unfortunately isn't likely to change), I am considering making a breaking change to CSLA .NET in version 3.5.
The CSLA base classes currently override System.Object.Equals() to provide the concept of logical equality, so two objects that return the same GetIdValue() value are considered to be equal.
WPF treats Equals() like ReferencesEquals() however (even though they aren't the same thing) and so it gets confused when any two object instances claim to be equal to each other. This confusion expresses itself as a flaw in data binding, where the UI doesn't rebind to a new object if it is "equal" to the one already bound to the UI.
So I am considering removing the Equals() override in the CSLA base classes. This would effectively make Equals() be like ReferenceEquals(), because that's the default behavior of Equals(). Each object would be equal only to itself.
If your application relies on logical equality, it would break. Now the reality is that most applications would be unaffected, because most people don't actually care a lot about equality. Basic operations using lists and data binding don't really care about logical equality, as long as objects can be uniquely identified.
You would likely be affected only if you explicitly compare for equality between objects in your business code, and do desire two object instances with the same key value from GetIdValue() to be considered as equal.
So I am starting this thread to get a sense for how many people would be affected, and thus to determine whether a more complex solution/workaround is required.
tetranz:Rocky, would this break the use of the IndexOf method of a collection?
No, because the object would still be equal to itself, so IndexOf() would continue to work
x = new Customer();
list.Add(x);
int pos = list.IndexOf(x);
That works fine either way, because x==x using all forms of equality.
What would NOT work is this:
x = new Customer();
list.Add(x);
y = x.Clone();
int pos = list.IndexOf(y);
Even though logically you can argue that y==x, using System.Object.Equals() they are not equal. This exemplifies the type of break you'd get in your code - and I think this is relatively rare?
But this will affect windows forms in things like combos.
Imagine a combo with a ReadOnlyList that binds a class with a property of combo's readonlylist item type. I Think that the combo syncronizes his items comparing with equals function.
Angel:But this will affect windows forms in things like combos.
Imagine a combo with a ReadOnlyList that binds a class with a property of combo's readonlylist item type. I Think that the combo syncronizes his items comparing with equals function.
No, it should have no impact. A ComboBox bound to a NVL or other list has two parts: the key and the value. The value is displayed to the user, the key is used to index back into the list to find the item. At no point is Equals() used to locate the item in the list - the key value is used to do that.
We just implemented 3.5 (3.5.1 actually), and the combobox problem has occurred for us. We have standard windows forms ComboBox classes that are not bound, but we add items to them from a ReadOnlyListBase. Later in the code, we try:
comboBox123.SelectedItem = MyReadOnlyListBase.GetListItem(1);
and this no longer works. The SelectedItem property is not set to the item returned by GetListItem because it no longer thinks they are equal.
I am investigating overriding Equals in our custom classes derived from the CSLA classes - but this is not as simple as I first thought it would be.
The light bulb always turns on for me right after I post.
I was attempting to override the Equals in the collection classes, not the object classes. Once I overrode Equals and GetHashCode in our object class (using the Equals and GetHashCode from CSLA 3.0 with slight modifications), our problems appear to be resolved. Thanks for this thread!
That Equals() thing is just a mess – darn the WPF guys for
changing the definition of the function…
You should be able to implement Equals() like it was in CSLA 3.0
in your custom base classes. The GetIdValue() method still exists, it is just
not required anymore. But if your classes implement GetIdValue() as a
matter of practice, then that Equals() override should function as it did
before.
Rocky
From: RobKraft
[mailto:cslanet@lhotka.net]
Sent: Thursday, July 24, 2008 3:30 PM
To: rocky@lhotka.net
Subject: Re: [CSLA .NET] Possible CSLA .NET 3.5 change (Equals())
We just implemented 3.5
(3.5.1 actually), and the combobox problem has occurred for us. We have
standard windows forms ComboBox classes that are not bound, but we add
items to them from a ReadOnlyListBase. Later in the code, we try:
comboBox123.SelectedItem = MyReadOnlyListBase.GetListItem(1);
and this no longer
works. The SelectedItem property is not set to the item returned by
GetListItem because it no longer thinks they are equal.
I am investigating overriding
Equals in our custom classes derived from the CSLA classes - but this is not as
simple as I first thought it would be.
I haven’t tested that, but that seems like a good assumption.
Rocky
From: paupdb
[mailto:cslanet@lhotka.net]
Sent: Thursday, July 24, 2008 5:51 PM
To: rocky@lhotka.net
Subject: Re: [CSLA .NET] RE: Possible CSLA .NET 3.5 change (Equals())
I assume Silverlight - being a nephew of WPF - has the same
Equals implementation as WPF?
If I pass a parameter named "key" to a method: key As Object
Where key is a numeric value(Int16, 32,64) or a String,
And then use this code:
If someBO.GetIdValue.Equals(key) Then
Will there be an issue?Joe
JoeFallon1:If I pass a parameter named "key" to a method: key As Object
Where key is a numeric value(Int16, 32,64) or a String,
And then use this code:
If someBO.GetIdValue.Equals(key) Then
Will there be an issue?Joe
There shouldn't be any issue. I'm not proposing getting rid of GetIdValue(). I am just proposing that it wouldn't be used for the Equals() (and related GetHashCode()) overrides. To turn that around, it would only be used for the default ToString() override, and in any code like you describe where you do call GetIdValue() directly.
Yes, that sounds like it would be broken.
A quicker fix (if you don’t plan to use WPF) would be to
override Equals() in your base classes just like CSLA does today – then you’d
continue to get exactly the same behavior as you already have.
Rocky
From: tetranz
[mailto:cslanet@lhotka.net]
Sent: Wednesday, November 14, 2007 3:23 PM
To: rocky@lhotka.net
Subject: Re: [CSLA .NET] Possible CSLA .NET 3.5 change (Equals())
This would break some of my code and necessitate some
recoding but not a big deal if its for the greater good.
I have an observer pattern thing where my "readonly" lists of info
objects (mostly for grids) get updated when some events occur. I usually know
the id but need the index so I tend to create an throwaway info object with
everything blank except the id and use that with IndexOf.
Since I have my own base objects between CSLA and my app I think a quick fix
will be to create my own IndexOf in my ReadOnlyList base to hide the underlying
IndexOf. I'm only doing Windows Forms for now.
Ross
FWIW, I don't think that would be a problem for us. And we'd be willing to accept such a small breaking change anyway if we were to upgrade.
Who would have thought that equality was such a tricky subject? We had a problem recently due to the fact that we had used the equality operator (i.e. ==) instead of the Equals() method. The "expected" results you get for value types and reference types are different!
Doesn't seem like big trouble to me. But it would be nice if you could make it configurable (using some global setting or a #define or whatever... so we could get the old behavior without messing with CSLA code or introducing intermediary base classes ?
I could do that sort of configuration, but I’m really not
sure it is worth it. So far it sounds like I will only break one person, and I’m
pretty sure they are doing what I recommend by having custom base classes J
Rocky
From: robert_m
[mailto:cslanet@lhotka.net]
Sent: Sunday, November 18, 2007 7:40 AM
To: rocky@lhotka.net
Subject: Re: [CSLA .NET] Possible CSLA .NET 3.5 change (Equals())
Doesn't seem like big trouble to me. But it would be nice if you
could make it configurable (using some global setting or a #define or
whatever... so we could get the old behavior without messing
with CSLA code or introducing intermediary base classes ?
Well, I just upgraded to CSLA 3.5 and this blew me right out of the water...
:(
Is there any harm in me implementing the override myself in my common BusinessBase<T> derived class?
#if VALUEEQUALITY
/// <summary>
/// Implement equality based on primary key
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public override bool Equals(object obj)
{
if (obj is T)
{
return Object.Equals(GetIdValue(), ((T)obj).GetIdValue());
}
else
return false;
}
#endif
Among other things, I do things like updating objects in lists on the screen in place after a transaction versus refetching the list, and since a saved or refetched individual object will no longer be "equal" to the stale list value you're trying to replace, it no longer finds the objects in the list?
The reason I made this change was because the two strategic UI
technologies offered by Microsoft aren’t compatible with logical equality.
The only issue you’ll encounter is that you won’t be
able to use WPF or Silverlight, because they won’t like your Equals()
override. So you’ll be stuck in Windows Forms and/or ASP.NET.
Rocky
If the problem areas in WPF and Silverlight are depending on reference equality, why don't they explicitly test for it using ReferenceEquals instead of using Equals?
I think the impact of this change is more insidious than may appear at first glance. Any code that was previously storing any BusinessBase object in a collection could previously determine if an object was already in the collection using built-in methods without worry about reference equality. Now every place where these methods would previously return true, and index, etc, they will indicate the object is not present.
Object caches, for example, may not work any more. Every Save() of an object renders a prior reference invalid, so what was previously a cache hit will now potentially be a miss, etc.
I can’t explain why WPF didn’t use ReferenceEquals()
like I would have expected… All I can do is live in the world they
create, for better or worse.
Rocky
^^^^
Perhaps you could use your vast influence to help them see the error of their ways... :)
Copyright (c) Marimer LLC