3

I've got a big project that I'm just getting around to testing for the first time in Release mode, and I've found a big problem. This code finds all objects that are in the currently visible list but not in the database, and adds them to another list for later removal. Normally, if there are no differences, toRemove remains empty. But in Release mode, toRemove becomes populated with the entire visibleList when there are no differences.

// Find which elements are in the visible list that do not exist in the database
foreach(var tr in visibleList.Where((entry) =>
   {
       return fullList.Contains(entry);
   }))
{
   toRemove.Add(tr);
}

After tearing apart the code and running some tests, I narrowed the problem down to this:

// Returns true in DEBUG mode, but false in RELEASE mode
//  (when entry does in fact equal fullList[0])
bool equalityResult = entry.Equals(fullList[0]);

fullList and toRemove are just basic C# List<Entry> objects, and visibleList is an ObservableCollection<Entry>.

Entry.Equals is not overloaded.

Why would this function behave differently between the two configurations? What can I do to fix this?

EDIT: The bulk of the Entry definition is:

public class Entry : INotifyPropertyChanged
{
    public String Name { get; set; }
    public String Machine { get; set; }
    public Int32 Value { get; set; }

    // Output values separated by a tab.
    public override string ToString()
    {
        return String.Format("{0}\t{1}\t{2}", Name, Machine, Value);
    }

    public String ToCSVString()
    {
        return String.Format("{0},{1},{2}", Name, Machine, Value);
    }

    #region WPF Functionality
    // Enable one-way binding in WPF
    public event PropertyChangedEventHandler PropertyChanged;
    protected void NotifyPropertyChanged(string name)
    {
        PropertyChangedEventHandler h = PropertyChanged;
        if (h != null)
        {
            h(this, new PropertyChangedEventArgs(name));
        }
    }
    #endregion

    // ...
}

EDIT: I implemented Entry.Equals, and that fixed the problem. Turns out I had some linking errors on top of everything that caused the Entry.Equals change in my code to be excluded from release builds. Having fixed that, and having implemented Equals, everything works like a charm. It makes me sad that I have to override that method though, seems like a bit too much work.

Sessamekesh
  • 420
  • 1
  • 6
  • 10
  • `Entry` is a class. It doesn't have anything very exotic, it just holds data that I read in from a database. Six or so string values, and a couple of functions for formatting them. – Sessamekesh Jul 17 '14 at 21:25
  • 2
    post the declartion of `Entry` – Brandon Jul 17 '14 at 21:25
  • 1
    Replace Equals by ReferenceEquals and see if the difference goes away. It shouldn't. Then, find out why sometimes all "entries" are references to the same object. In other words, look at the code that generates the list. – usr Jul 17 '14 at 21:27
  • Did you write an override for `Equals`? Where is that? – jamesSampica Jul 17 '14 at 21:28
  • I tried overriding `Equals` to check my members, it didn't work. I tried replacing `Equals` with `ReferenceEquals`, it would return false in both debug and release (which is a start, but I'm looking for a 'true' in both cases. – Sessamekesh Jul 17 '14 at 21:31
  • See detailed information and sample stations here MSDN [Object.Equals Method (Object) (System)](http://msdn.microsoft.com/en-us/library/bsc2ak47%28v=vs.110%29.aspx) – khan Jul 17 '14 at 21:39

2 Answers2

0

If you have not defined an Equals implementation in your Entry class, then, assuming it is a class and not a struct, Equals by default only performs a reference comparison. See What is the default behavior of Equals Method?

For instance:

public class AgeWrapper {
    public int Age { get; set; }
    public AgeWrapper( int age ) { this.Age = age; }
}

public void DoWork() {
   AgeWrapper a = new AgeWrapper(21);
   AgeWrapper b = new AgeWrapper(21);
   AgeWrapper c = a;

   Console.WriteLine( a.Equals(b) ); // prints false;

   Console.WriteLine( a.Equals(c) ); // prints true;
}

The only way to make it work they way you expect, is to provide your own Equals comparison.

And since you will be doing that, you will need to override GetHashCode so that the two generate consistent values. The Amazing Jon Skeet can help you with the proper way to do that.

You do not want ReferenceEquals - you care about the values contained in your object, not where they happen to be stored in memory.

public class Entry : INotifyPropertyChanged
{
    public String Name { get; set; }
    public String Machine { get; set; }
    public Int32 Value { get; set; }

    public override bool Equals( object other ) 
    {
        Entry otherEntry = other as Entry;

        if ( otherEntry == null ) { return false; }

        return 
            otherEntry.Name.Equals( this.Name ) &&
            otherEntry.Machine.Equals( this.Machine ) &&
            otherEntry.Value.Equals( this.Value );
     }


     public override int GetHashCode()
     {
          // Thanks Jon Skeet!
          unchecked // Overflow is fine, just wrap
          {
              int hash = (int) 2166136261;

              hash = hash * 16777619 ^ this.Name.GetHashCode();
              hash = hash * 16777619 ^ this.Machine.GetHashCode();
              hash = hash * 16777619 ^ this.Value.GetHashCode();

              return hash;
          }
     }
}

The above assumes that Name, Machine and Value define the identity of your object.

Community
  • 1
  • 1
antiduh
  • 11,853
  • 4
  • 43
  • 66
  • 1
    See, I thought of that, but it behaves differently in _Debug_ but not in _Release_. Is `Equals` by reference in _Release_ but by value in _Debug_? I also tried overloading `Equals`, and that didn't help. – Sessamekesh Jul 17 '14 at 21:27
  • @user2589028 - You must've gotten lucky in Debug mode, and the object references that you're performing comparisons with were the same references. Something else in Release mode is causing you to perform comparison with cloned objects. – antiduh Jul 17 '14 at 21:34
  • I'll give that another go. Thanks! – Sessamekesh Jul 17 '14 at 21:36
0

Equals method, unless overriden, does a reference comparison for classes, regardless of being in a debug or a release build.

So, your problem is that the list contains copies (clones) of your objects, probably after a roundtrip to the database through your favorite ORM. If you examine their properties inside the debugger, they will look identical, but as far as Equals is concerned, these are different instances.

It's hard to say where is the difference between your builds (is your ORM configured differently?), but you in fact probably want to create your own Equals override (or a custom IEqualityComparer implemenatation) to make sure that you can compare objects which were instantiated by the ORM.

Community
  • 1
  • 1
vgru
  • 49,838
  • 16
  • 120
  • 201