Stavros
Stavros

Reputation: 23

Removing Object From List(Of T)... problems

I need to get to the bottom of this as I have researched the issue but I just do not get it.

Simple Object Here:

Dim Person As New Person
Dim myPeopleList as New List(Of Person)
Person.Name = "John"
Person.Age = 33
myPeopleList.Add(Person)

Works... BUT

MyPeopleList.Remove(Person)

Does not work.

What am I missing here? What can I do instead of looping through MyPeopleList comparing the name or the age

Upvotes: 0

Views: 74

Answers (2)

Caius Jard
Caius Jard

Reputation: 74605

If you do this:

    Dim myPeopleList As New List(Of Person)

    Dim person As New Person
    person.Name = "John"
    person.Age = 33
    myPeopleList.Add(person)

    myPeopleList.Remove(person)

It works fine. This is because your Person inherits from Object, and in .NET by default two objects are equal if their memory address is equal. In this example above, the memory addresses are by definition equal because the same variable reference is used for the add (hence held in the list) as it then also passed in for remove.

It also means you could do this:

    Dim person As New Person
    person.Name = "John"
    person.Age = 33
    myPeopleList.Add(person)

    Dim otherPerson = person         '
    myPeopleList.Remove(otherPerson)

The remove will still work because otherPerson and person are a reference to the same data at the same memory address

This, however, won't work:

    Dim person As New Person
    person.Name = "John"
    person.Age = 33
    myPeopleList.Add(person)

    Dim otherPerson As New Person
    otherPerson.Name = "John"
    otherPerson.Age = 33

    myPeopleList.Remove(otherPerson)

Because even though the data inside the person is the same, they aren't the same object at the same memory location. If you were to take a dump of your memory, you'd find it like ...John..33..........John..33...... - literally there are two different objects that happen to have the same data

Remember, by default (unless we decide otherwise) objects are compared based on where they are in memory (it's a pretty good default really; two objects can't occupy the same space)

So let's decide otherwise. Let's make a rule that a Person equals another Person if their Name and Age are the same value:

Class Person
    Public Age As Int32
    Public Name As String

    Public Overrides Function Equals(obj As Object) As Boolean
        Dim p = DirectCast(obj, Person)
        Return p.Name = Me.Name AndAlso p.Age = Me.Age
    End Function
End Class

If we override the default Equals (which compares memory addresses) with our own version that compares a passed-in object (which is expected to be a person in this case) values for Name and Age with the current values, then all of a sudden we can do this:

    Dim person As New Person
    person.Name = "John"
    person.Age = 33
    myPeopleList.Add(person)

    Dim otherPerson As New Person
    otherPerson.Name = "John"
    otherPerson.Age = 33

    myPeopleList.Remove(otherPerson)

When it's doing the remove, List will visit every item in its internal array, asking "this passed in otherPerson .Equals the one in my list?" and when our version of Equals, which has replaced the default version, returns True, the list can say "ah, this person in my array is the person being sought for removal - remove it"


A couple of things worth pointing out: First that:

  • If you override Equals, you should also override GetHashCode as well. Some collection classes, like Dictionary, will rely on GetHashCode to make a first stab at assessing whether they index a particular item in their Keys collection, before they use Equals to confirm. If you don't override GetHashCode so it can be quickly used to assess whether two objects are different, than you can end up with a situation where a Dictionary considers two objects not equal but a List considers them equal. For example with our Person above, where we override Equals but not GetHashCode, and made two new Person objects with the same name/age then list.Add(person) list.Contains(otherPerson) would return true, but dictionary.Add(person, ...) dictionary.ContainsKey(otherPerson, ...) would return False
  • Two objects that are equal should have the same hashcodes. Two objects that are not equal may also randomly produce the same hashcodes. Collections that use hashcodes are geared up to expect this, and will use Equals to truly determine equality after they use the hashcode to quickly locate the object in their collection

Second:

  • Think carefully about how you define objects to be equal. As Etienne points out in the comments, if your equality check is Name and Age, then any 33 yr old called John is considered equal to any other. If your list contains mutliple people that are equal under the rule, then list will remove the first 33 year old John it finds when asked to do so. If it's not the John you were expecting, it's an indication that the rule of equality needs to be improved

Upvotes: 3

David
David

Reputation: 6111

My guess is that when you are trying to remove an item, you aren't passing the same reference as the one that was added.

If you want to remove the item where the person's name is John and who is 33 years old, then you should query the collection for the item first, then remove the returned result:

Dim john = myPeopleList.Single(Function(person) person.Name = "John" AndAlso person.Age = 33)
myPeopleList.Remove(john)

The issue that you can run into this is what would happen if there is more than one John who is 33 years old or there is none at all? The example I just gave would fail because Single expects there to be exactly one instance.

If this can occur, then an alternative would be to modify the underlying list to only include people who do not meet your conditions:

myPeopleList = myPeopleList.Where(Function(person) person.Name <> "John" AndAlso person.Age <> 33).ToList()

Upvotes: 0

Related Questions