8

I have the following class

public class ModInfo : IEquatable<ModInfo>
{
    public int ID { get; set; }
    public string MD5 { get; set; }

    public bool Equals(ModInfo other)
    {
        return other.MD5.Equals(MD5);
    }

    public override int GetHashCode()
    {
        return MD5.GetHashCode();
    }
}

I load some data into a list of that class using a method like this:

public void ReloadEverything() {
    var beforeSort = new List<ModInfo>();
    // Bunch of loading from local sqlite database. 
    // not included since it's reload boring to look at
    var modinfo = beforeSort.OrderBy(m => m.ID).AsEnumerable().Distinct().ToList();
}

Problem is the Distinct() call doesn't seem to do it's job. There are still objects which are equals each other.

Acording to this article: https://msdn.microsoft.com/en-us/library/vstudio/bb348436%28v=vs.100%29.aspx that is how you are supposed to make distinct work, however it doesn't seem to be calling to Equals method on the ModInfo object. What could be causing this to happen?

Example values:

modinfo[0]: id=2069, MD5 =0AAEBF5D2937BDF78CB65807C0DC047C
modinfo[1]: id=2208, MD5 = 0AAEBF5D2937BDF78CB65807C0DC047C

I don't care which value gets chosen, they are likely to be the same anyway since the md5 value is the same.

Rasmus Hansen
  • 1,502
  • 1
  • 17
  • 28

3 Answers3

10

You also need to override Object.Equals, not just implement IEquatable.

If you add this to your class:

public override bool Equals(object other)
{
    ModInfo mod = other as ModInfo;
    if (mod != null)
        return Equals(mod);
    return false;
}

It should work.

See this article for more info: Implementing IEquatable Properly

EDIT: Okay, here's a slightly different implementation based on best practices with GetHashCode.

public class ModInfo : IEquatable<ModInfo>
{
    public int ID { get; set; }
    public string MD5 { get; set; }

    public bool Equals(ModInfo other)
    {
        if (other == null) return false;
        return (this.MD5.Equals(other.MD5));
    }

    public override int GetHashCode()
    {
        unchecked
        {
            int hash = 13;
            hash = (hash * 7) + MD5.GetHashCode();
            return hash;
        }
    }

    public override bool Equals(object obj)
    {
        ModInfo other = obj as ModInfo;
        if (other != null)
        {
            return Equals(other);
        }
        else
        {
            return false;
        }
    }
}

You can verify it:

ModInfo mod1 = new ModInfo {ID = 1, MD5 = "0AAEBF5D2937BDF78CB65807C0DC047C"};
ModInfo mod2 = new ModInfo {ID = 2, MD5 = "0AAEBF5D2937BDF78CB65807C0DC047C"};

// You should get true here
bool areEqual = mod1.Equals(mod2);

List<ModInfo> mods = new List<ModInfo> {mod1, mod2};

// You should get 1 result here
mods = mods.Distinct().ToList();

What's with those specific numbers in GetHashCode?

Community
  • 1
  • 1
Fred Kleuver
  • 7,797
  • 2
  • 27
  • 38
  • That will only compare the reference, not the actual values of the object will it not? – Rasmus Hansen Apr 06 '15 at 11:33
  • No, not unless the implementation of `ModInfo.Equals(ModInfo other)` does that. – Jurgen Camilleri Apr 06 '15 at 11:42
  • This `Equals` method is calling your equals method, which does the actual comparison. You might have misunderstood me: you need to **add** this method to your class, not replace your method with mine. – Fred Kleuver Apr 06 '15 at 11:54
  • I agree that this should work, but it still seems not to be the case. – Rasmus Hansen Apr 06 '15 at 11:55
  • What is your implementation of GetHashCode? – Fred Kleuver Apr 06 '15 at 11:57
  • I tried with both public override int GetHashCode() { return 0; } (Here the Equals method gets called, but the list still contains dublicate objects) and public override int GetHashCode() { return MD5.GetHashCode(); } (Here the Equals method never gets called and there are dublicate objects) – Rasmus Hansen Apr 06 '15 at 12:00
  • I provided a more complete example, can you let me know if it works? – Fred Kleuver Apr 06 '15 at 12:14
  • Still not working. I'll push my code to github and provide a complete link so you see it in the context it's working in. – Rasmus Hansen Apr 06 '15 at 12:20
  • That would be great, because I literally ran this code myself and it worked. I wonder what's going on! ;) – Fred Kleuver Apr 06 '15 at 12:21
  • Method: https://github.com/zlepper/TechnicSolderHelper/blob/master/TechnicSolderHelper/SQL/ModListSQLHelper.cs#L68-L131 Modlist: https://github.com/zlepper/TechnicSolderHelper/blob/master/TechnicSolderHelper/SQL/ModListSQLHelper.cs#L11-L52 DB file to be put in "C:\Users\User\AppData\Roaming\SolderHelper" https://dl.dropboxusercontent.com/u/45252097/SolderHelper.db – Rasmus Hansen Apr 06 '15 at 12:25
  • I ran your code locally and found that a total of 496 ModInfo objects come out of your database. I then output all their MD5 property values to an Excel file and checked for duplicates inside Excel, but none were found. Here is the Excel: http://s000.tinyupload.com/?file_id=06870272569563360623 so if MD5 is your criterium for determining uniqueness, then all of them are unique to begin with. Are you sure that MD5 is the correct value to check on? – Fred Kleuver Apr 06 '15 at 13:06
  • Counting the objects at the time I do the where check actually shows that some are getting added after I read the info from the database, here among the dublicate md5. So now I just need to stop that, and everything will be good. – Rasmus Hansen Apr 06 '15 at 13:40
0

Add

public bool Equals(object other)
    {
        return this.Equals(other as ModInfo)
    }

Also see here the recommendations how to implement the equality members: https://msdn.microsoft.com/en-us/library/ms173147(v=vs.80).aspx

Radin Gospodinov
  • 2,313
  • 13
  • 14
  • That will only compare the reference, not the actual values of the object will it not? To add to that I don't care about the ID (And a bunch of other values on my objects) I only care about the md5 being equal. – Rasmus Hansen Apr 06 '15 at 11:33
  • No, it will call public bool Equals(ModInfo other) method, because other is converted to ModInfo. Also amend to the following to handle the case when "other" is null: public bool Equals(ModInfo other) { if(other == null){return false;} return other.MD5.Equals(MD5); } – Radin Gospodinov Apr 06 '15 at 11:35
0

You could also use IEqualityComparer.

.Distinct(new ModInfoComparer())

Link to Microsoft docs.

Malt
  • 151
  • 1
  • 3