1

It seems that this problem has already been encountered by quite a few people:

List not working as expected

Contains always giving false

So I saw the answers and tried to implement the override of Equals and of GetHashCode but there seems that I am coding something wrong.

This is the situation: I have a list of Users(Class), each user has a List and a Name property, the list property contains licenses. I am trying to do a

         if (!users.Contains(currentUser))

but it is not working as expected. And this is the code I did to override the Equals and GetHashCode:

    public override bool Equals(object obj)
    {
        return Equals(obj as User);
    }


    public bool Equals(User otherUser)
    {
        if (ReferenceEquals(otherUser, null))
            return false;

        if (ReferenceEquals(this, otherUser))
            return true;

        return this._userName.Equals(otherUser.UserName) && 
               this._licenses.SequenceEqual<string>(otherUser.Licenses);
    }

    public override int GetHashCode()
    {
        int hash = 13;
        if (!_licenses.Any() && !_userName.Equals(""))
        {
            unchecked
            {
                foreach (string str in Licenses)
                {
                    hash *= 7;
                    if (str != null) hash = hash + str.GetHashCode();
                }
                hash = (hash * 7) + _userName.GetHashCode();
            }
        }
        return hash;
    }

thank you for your suggestions and help in advance!

EDIT 1:

this is the code where I am doing the List.Contains, I am trying to see if the list already contains certain user, if not then add the user that isn't there. The Contains only works the first time, when currentUser changes then the User inside the list changes to the current user maybe this is a problem that is unrelated to the equals, any ideas?

        if (isIn)
        {
            if (!listOfLicenses.Contains(items[3]))
                listOfLicenses.Add(items[3]);

            if (!users.Contains(currentUser))
            {
                User user2Add = new User();
                user2Add.UserName = currentUser.UserName;
                users.Add(user2Add);
                userIndexer++;
            }

            if (users[userIndexer - 1].UserName.Equals(currentUser.UserName))
            {
                users[userIndexer - 1].Licenses.Add(items[3]);
            }
            result.Rows.Add();
        }
Community
  • 1
  • 1
Jose Luis
  • 3,307
  • 3
  • 36
  • 53
  • Does the list of Licenses change? Does the `_userName` change? – Enigmativity Oct 07 '11 at 06:50
  • Have you stepped through in a debugger to see which line is producing the incorrect results? – Dirk Oct 07 '11 at 06:53
  • I would delete the `if (!_licenses.Any() && !_userName.Equals(""))` because it's quite useless, but other than this, it seems to be ok. Are you sure your `Licenses` are in the same order? – xanatos Oct 07 '11 at 06:53
  • what do you mean? I go through a log file and the currentUser constantly changes while StreamReading the file. For some reason the Contains only works once (the first time) then when currentUser changes the inside of the user list changes aswell. I guess this is not very clear so I'll post the corresponding code in an edit. – Jose Luis Oct 07 '11 at 06:56
  • @L.B i dont understand what you mean can you explain further? :-) – Jose Luis Oct 07 '11 at 07:07

5 Answers5

2

Well, one problem with your hash code - if either there are no licences or the username is empty, you're ignoring the other component. I'd rewrite it as:

public override int GetHashCode()
{
    unchecked
    {
        int hash = 17;
        hash = hash * 31 + _userName.GetHashCode();
        foreach (string licence in Licences)
        {
            hash = hash * 31 + licences.GetHashCode();
        }
        return hash;
    }
}

Shorter and simpler. It doesn't matter if you use the hash code of the empty string, or if you iterate over an empty collection.

That said, I'd have expected the previous code to work anyway. Note that it's order sensitive for the licences... oh, and List<T> won't use GetHashCode anyway. (You should absolutely override it appropriately, but it won't be the cause of the error.)

It would really help if you could show a short but complete program demonstrating the problem - I strongly suspect that you'll find it's actually a problem with your test data.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Very good suggestion, I will try to make it separately in a new project and will keep you posted. – Jose Luis Oct 07 '11 at 07:01
  • I am rather unable to make a short but complete program since that involves probably making an almost the same project again. I think it has to do with it being order sensitive, Do you know how can I make it order insensitive? :) – Jose Luis Oct 07 '11 at 07:26
  • @Joze: Why would it involve creating the same project again? It just needs the User class and a tiny console app to create two users who you think should be equal but aren't. As for the order sensitivity - if these are meant to be *sets* of licences, then I would suggest keeping them in a `HashSet` - you can then use the `SetEquals` method. – Jon Skeet Oct 07 '11 at 08:03
  • Okay the problem seems to be solved and it was rather with the license order I'm not sure though. Thank you very much for your help and time in any case! – Jose Luis Oct 07 '11 at 08:18
  • @Joze: You should really decide how you want to consider the collection of licences. Some options: always sorted; arbitrary order but it's important; arbitrary unimportant order (a set). Then you can use the right collection for your needs. (You should also consider whether you want to allow duplicates.) – Jon Skeet Oct 07 '11 at 08:34
  • I see, well the order really doesn't matter and about the duplicates I have to count them later so I will allow them until counted and then delete them... I don't know the advantages I can get with a set since the list is for now working well... Thank you! – Jose Luis Oct 07 '11 at 08:43
1

After users[userIndexer - 1].Licenses.Add(items[3]) , users[userIndexer - 1] is not the same user anymore. You have changed the Licences which is used in equality comparison(in User.Equals).

--EDIT See below code

public class Class
{
    static void Main(string[] args)
    {
        User u1 = new User("1");
        User u2 = new User("1");
        Console.WriteLine(u1.Equals(u2));
        u2.Lic = "2";
        Console.WriteLine(u1.Equals(u2));
    }
}

public class User
{
    public string Lic;        
    public User(string lic)
    {
        this.Lic = lic;
    }

    public override bool Equals(object obj)
    {
        return (obj as User).Lic == Lic;
    }
}
L.B
  • 114,136
  • 19
  • 178
  • 224
  • Oh Now I get it thank you, I'll check this if it influences it. – Jose Luis Oct 07 '11 at 07:24
  • The thing is that I am adding the License only if the UserName is the same which to me makes sense. It doesn't matter if the user is not the same (with license difference) what is important is that their Username is the same so that the new license can be added instead of a totally new user. Do you have any suggestions to fix this? – Jose Luis Oct 07 '11 at 07:40
  • Just comparing names in `Equals` function? – L.B Oct 07 '11 at 07:53
0

You need you implement Equals and GetHashcode for the License class, otherwise SequenceEqual will not work.

gcores
  • 12,376
  • 2
  • 49
  • 45
  • There is no License class. License is a common string. What can I use if I can't use SequenceEqual? – Jose Luis Oct 07 '11 at 06:54
  • Oh, Then it should work fine, only thing I can think of is if the order of licenses is wrong. I recommend the debugger. – gcores Oct 07 '11 at 06:58
0

Does your class implement IEquatable<User>? From your equality methods it appears it does but just checking.

The documentation for List.Contains states that:

This method determines equality by using the default equality comparer, as defined by the object's implementation of the IEquatable(T).Equals method for T (the type of values in the list)

Ronald Wildenberg
  • 31,634
  • 14
  • 90
  • 133
0

It is very important to make sure that the value returned by GetHashCode never ever ever changes for a specific instance of an object. If the value changes then lists and dictionaries won't work correctly.

Think of GetHashCode as "GetPrimaryKey". You would not change the primary key of a user record in a database if someone added a new license to the user. Likewise you mustn't change the GetHashCode.

It appears from your code that you are changing the licenses collection and you're using that to calculate your hash code. So that is probably causing your issue.

Now, it is perfectly legitimate to use a constant value for every hash code you produce - you could just return 42 for every instance, for example. This will force calling Equals to determine if two objects are equal or not. All that having distinct hash codes does is short circuits the need to call Equals.

If the _userName field doesn't change then just return its hash code and see it that works.

Enigmativity
  • 113,464
  • 11
  • 89
  • 172