Overriding Equals method not being called when doing IndexOf an item in an ArrayList

J

Jay B. Harlow [MVP - Outlook]

Doh!

In the CollectionBase version of PersonCollection (the 2nd example) I can
simplify the Find function by declaring Person inline instead of defining
the T type parameter.

Instead of:

| Public Class PersonCollection
| Inherits CollectionBase

| Public Function Find(Of T, V)(ByVal value As V, _
| ByVal predicate As Predicate(Of T, V)) As T
| For Each item As T In Me.InnerList
| If predicate(item, value) Then
| Return item
| End If
| Next
| End Function

I should have used:

Public Function Find(Of V)(ByVal value As V, _
ByVal predicate As Predicate(Of Person, V)) As Person
For Each item As Person In Me.InnerList
If predicate(item, value) Then
Return item
End If
Next
End Function

| End Class

Which then allows you to call it without the type parameters (V is able to
be inferred).

Dim foundPerson As Person = people.Find _
("jay", AddressOf Person.CompareName)

Hope this helps
Jay

| John,
| Here is an example based on Generics in VS.NET 2005. Plus some additional
| info on non-delegate based Criteria.
|
| Instead of a Delegates, you could define the Criteria as a "Query Object"
| pattern. http://www.martinfowler.com/eaaCatalog/queryObject.html. I would
| expect the delegate might be easier to implement, however the Query Object
| might offer less coupling, especially if the Query Object was based on
| reflection...
|
|
| The "easiest" method to use a Generic Delegate is to start with
| Collection(Of T). Collection(Of T) is the generic version of
CollectionBase.
| Something like:
|
| ' VS.NET 2005 syntax
| Imports System.Collection.ObjectModel
|
| Public Delegate Function Predicate(Of T, V)(ByVal item As T, ByVal
value
| As V) As Boolean
|
| Public Class CollectionBase(Of T)
| Inherits Collection(Of T)
|
| Public Overloads Function IndexOf(Of V)(ByVal value As V, _
| ByVal predicate As Predicate(Of T, V)) As Integer
| For index As Integer = 0 To Me.Count - 1
| If predicate(Me(index), value) Then
| Return index
| End If
| Next
| Return -1
| End Function
|
| Public Function Find(Of V)(ByVal value As V, _
| ByVal predicate As Predicate(Of T, V)) As T
| For Each item As T In Me
| If predicate(item, value) Then
| Return item
| End If
| Next
| End Function
|
| End Class
|
| Public Class PersonCollection
| Inherits CollectionBase(Of Person)
|
| ' yes its really empty!
|
| End Class
|
| Public Enum JobCategory
| Category1 = 1
| Category2 = 2
| End Enum
|
| Public Class Person
|
| Public ReadOnly Name As String
| Public ReadOnly JobCategory As String
|
| Public Sub New(ByVal name As String, ByVal jobCategory As String)
| Me.Name = name
| Me.JobCategory = jobCategory
| End Sub
|
| Public Shared Function CompareName(ByVal item As Person, _
| ByVal value As String ) As Boolean
| Return item.Name = value
| End Function
|
| Public Shared Function CompareJobCategory(ByVal item As Person, _
| ByVal value As JobCategory) As Boolean
| Return item.JobCategory = value
| End Function
|
| End Class
|
| Public Sub Main()
| Dim people As New PersonCollection
| people.Add(New Person("jay", JobCategory.Category2))
| people.Add(New Person("john", JobCategory.Category1))
|
| Dim index As Integer
|
| index = people.IndexOf("jay", AddressOf Person.CompareName)
| index = people.IndexOf(JobCategory.Category1, AddressOf
| Person.CompareJobCategory)
|
| Dim foundPerson As Person = people.Find("jay", AddressOf
| Person.CompareName)
|
| foundPerson = people.Find(JobCategory.Category2, AddressOf
| Person.CompareJobCategory)
| End Sub
|
| Starting with Collection(Of T) also simplifies your type safe class as the
| above shows.
|
|
| Alternatively you could start with CollectionBase, however its not as
clean
| as the above (I actually implemented this sample first).
|
| Public Class PersonCollection
| Inherits CollectionBase
|
| Default Public ReadOnly Property Item(ByVal index As Integer) As
| Person
| Get
| Return DirectCast(Me.InnerList(index), Person)
| End Get
| End Property
|
| Public Sub Add(ByVal person As Person)
| Me.InnerList.Add(person)
| End Sub
|
| Public Function IndexOf(ByVal value As Object) As Integer
| Return MyBase.InnerList.IndexOf(value)
| End Function
|
| Public Function IndexOf(Of V)(ByVal value As V, _
| ByVal predicate As Predicate(Of Person, V)) As Integer
| For index As Integer = 0 To Me.Count - 1
| If predicate(Me.Item(index), value) Then
| Return index
| End If
| Next
| Return -1
| End Function
|
| Public Function Find(Of T, V)(ByVal value As V, _
| ByVal predicate As Predicate(Of T, V)) As T
| For Each item As T In Me.InnerList
| If predicate(item, value) Then
| Return item
| End If
| Next
| End Function
|
| End Class
|
| Public Sub Main()
| Dim people As New PersonCollection
| people.Add(New Person("jay", JobCategory.Category2))
| people.Add(New Person("john", JobCategory.Category1))
|
| Dim index As Integer
|
| index = people.IndexOf("jay", AddressOf Person.CompareName)
| index = people.IndexOf(JobCategory.Category1, AddressOf
| Person.CompareJobCategory)
|
| Dim foundPerson As Person = people.Find(Of Person, String) _
| ("jay", AddressOf Person.CompareName)
|
| foundPerson = people.Find(Of Person, JobCategory) _
| (JobCategory.Category2, AddressOf
Person.CompareJobCategory)
|
| End Sub
|
| Notice when calling Person.Find in the second example I had to supply the
| type parameters, as they could not be inferred, in the first example, the
| parameters were either supplied in the class itself "Inherits
Collection(Of
| Person)" or were easily inferred by the parameters to Find...
|
| I would consider using the second method if I was migrating from an
existing
| VB.NET 2002 or VB.NET 2003 solution, until I was able to refactor the code
| to use the first method...
|
| Generics allow you to define types that have other types as parameters.
|
| Hope this helps
| Jay
|
| || Hi Jay,
||
|| I can appreciate your point of view... there are some good reasons to
|| limit indexof to an apples to apples comparison. However, I'd like to
| make
|| my case as to why is can be valuable to have a more intelligent
|| comparison.... and it's much more logical than, as you said, "comparing
|| apples to auto parts". (now, why would anybody want to do that??)
||
|| In our commercial application we have (so far) about 40 different custom
|| classes which define the specific things the system will deal with. For
| now
|| lets say we have an Employee class, a Job category class, and an Employee
|| Rating class.
|| In our actual application virtually everything that is a custom class is
|| stored in an arraylist so that it can be expanded at any time. In my
|| example it means that you can start off with 15 job categories, and add
25
|| more at a later date, without having to change 1 line of code. In some
|| classes we set up an ENUM for ease and clarity of coding, and some ENUMs
|| have 80 to 90 entries. To make matters more interesting, it is very
| common
|| that a class has a property that is another custom class. In the example
|| you could imagine that an instance of the Employee class has a property
| that
|| is an instance of the JobCategory class.
|| So the question (and it is a very serious question) is this: how can we
|| easily code a retrieval process that will retrieve a specific instance of
| a
|| custom class that is stored in one of the arraylists.
|| In different situations we may have different 'bits' of information to
|| identify the instance we want. For example, we might want to search for
a
|| Job Category of "17" (that's it's unique job code) OR we may want to
| search
|| for a Job Category of "Teacher" (that's it's unique name). Either one
is
|| enough to uniquely identify the instance of the JobCategory class we are
|| interested in. Now take this simple example and multiply it 100 fold
and
|| you begin to see why having a separate indexof for each possible way we
| want
|| to search would quickly get out of control.
|| The solution we are using involves a derived arraylist that has
overridden
|| the indexof method. All of our class instances are stored in these
custom
|| arraylists. The indexof method will call the object.equals method as
long
|| as the arraylist is not empty.
|| Each of the custom object.equals methods is pretty smart. They first
test
|| for type (ie: typeof xxx is string) and then test for whatever we need to
|| match in the class properties to see if we actually have a match. This
|| works very well.
|| Now, the thing that makes it all come together is this: the function
that
|| actually searches these arraylists has an overload for each of the
| different
|| arraylist collections. This way, we positively know what we can search
| on,
|| and when the function returns the class instance, it returns it CAST AS
| THE
|| ACTUAL CLASS, so we can use the dot nomenclature to obtain any other
|| property or access any other method of the class. The search function is
|| called FINDITEM and here is an example of how it might work: Let's say
| you
|| want to find the number of sick days employee 12345 has had year to date:
||
|| intNumSickDays = FINDITEM(alEmployeeCollection, new Employee,
|| 12345).NumSickDays
||
|| That's it. Pretty easy and straightforward. The first param is the
|| arraylist collection, the second is just a instance of the custom class
|| we're looking for (used to identify the proper overload), and the third
|| param is what we're searching for. Notice that we are accessing the
|| NumOfSickDays property DIRECTLY from the FindItem function. Better yet,
|| since our custom classes can be nested (as the example above) this search
|| function CAN ALSO BE NESTED! Complex, multilevel, searches through
|| multiple arraylists with one easily understood statement. It doesn't get
|| much better than that!
||
|| We have estimated that by using this technique we will save hundreds of
|| manhours in coding over the development of the project (think $$$), not
to
|| mention that our implimentation will be extremely extensible (everythings
|| stored in arraylists, which can be expanded), and extremely reliable
|| (basically one function to call to retrieve any data). The only
downside
|| to this would be performance. However, we made the decision to go with
| the
|| easy to use, bulletproof code because next year the computers will be
| twice
|| as fast as today anyway, so what the heck.
||
|| Well, Jay, that's my case. Hope I've made a convincing one...
||
|| John
||
||
||
||
||
||
||
|| || > John,
|| > | 2) If you pass anything other than an instance of your custom class
to
|| > the
|| > | indexof method (even if your object.equals code would know how to
| handle
|| > it)
|| > | the indexof method will always return -1 (not found).
|| > As I told Tom: I would expect that! As my understanding is that
|| > Object.Equals should only succeed if both types are identical or at
| least
|| > derived types. The Point example on the link I gave earlier
demonstrates
|| > this "rule", however I don't see the "rule" spelled out...
|| >
|| > Even with Derived types there is risk involved will allowing the
|| > comparison...
|| >
|| > | So what do you do if you have coded some intelligent object.equals
|| > methods
|| > | and want to take advantage of them?
|| > Unfortunately your "intelligent object.equals", now is comparing apples
| to
|| > auto parts. Comparing apples to auto parts is not really logical
|| > ("intelligent"). I can see comparing an Apple to an Orange, as they are
|| > both
|| > fruits. However! An Apple cannot be an Orange, only attributes of the
|| > Apple
|| > can be the same as attributes of the Orange, ergo you should be
| comparing
|| > the attributes of the Apple & Orange instead of attempting to compare
| the
|| > Apple & Orange directly!
|| >
|| > | Anyway, that's it. The above is based on my experimentation and works
|| > fine
|| > | for me... I hope this will help anybody else that is having issues
| with
|| > the
|| > | arraylist.indexof functionality.
|| > ArrayList.IndexOf seems to work as I would expect it to! As I showed in
| my
|| > sample earlier I don't allow my Object.Equals to compare objects of
|| > different types, (Person <> String). If I needed to search the
ArrayList
|| > for
|| > an object with a specific attribute I would define an alternate IndexOf
|| > (with a specific name) that did the look up.
|| >
|| >
|| > Public Function SearchForFirstName(name As String) As Integer
|| > For i As Integer = 0 To Me.Count - 1
|| > If Me.Item(i).FirstName = name Then
|| > Return index
|| > End If
|| > Next
|| > Return -1
|| > End Function
|| >
|| > As its obvious that SearchForFirstName is searching for an object with
a
|| > specific first name.
|| >
|| > If the object supported implicit or explicit conversion between types
|| > (current C# or VB.NET 2005 overloaded Widening or Narrowing CType
|| > operators), such as a String can be widened to a Categoryobject. I
would
|| > do
|| > this conversion before calling the IndexOf operator. Although there is
a
|| > conversion between String & Category, Category.Equals would still only
|| > compare two Category values.
|| >
|| > Hope this helps
|| > Jay
|| >
|| >
|| > || > | OK, first I want to thank all that contributed to this conversation.
|| > after
|| > | much experimenting I have (I think) narrowed down what is going on
|| > | concerning the ARRAYLIST.INDEXOF functionality.
|| > |
|| > |
|| > |
|| > | 1) in a normal arraylist containing custom class instances, if you
| pass
|| > an
|| > | instance of that custom class to the indexof method, it all works as
|| > | expected.
|| > |
|| > | Example: x = arraylist.indexof(mycustomclass)
|| > |
|| > | Indeed, the overridden object.equals method in your custom class is
|| > called
|| > | as verified by a breakpoint in the code.
|| > |
|| > |
|| > |
|| > | 2) If you pass anything other than an instance of your custom class
to
|| > the
|| > | indexof method (even if your object.equals code would know how to
| handle
|| > it)
|| > | the indexof method will always return -1 (not found).
|| > |
|| > |
|| > |
|| > | Example: x = arraylist.indexof(integer) à will always
|| > return -1
|| > |
|| > |
|| > |
|| > | I'm guessing that the standard indexof method initially checks the
| type
|| > of
|| > | the parameter and if it's not the same as the type contained in the
|| > | arraylist it chooses not to continue by calling object.equals. it
| just
|| > | gives up and returns a -1. The standard indexof method knows that
| the
|| > | standard object.equals method expects an instance of the 'myclass'
| class
|| > and
|| > | does not allow for the possibility that you have overridden the
|| > | object.equals method with a more intelligent one.
|| > |
|| > |
|| > |
|| > | So what do you do if you have coded some intelligent object.equals
|| > methods
|| > | and want to take advantage of them?
|| > |
|| > | Example: indexof(myclass) or indexof("string") or
|| > indexof(integer)
|| > | could all
|| > |
|| > | work because my object.equals method tests for the 'typeof'
|| > | parameter passed
|| > |
|| > | and would handle each case appropriately.
|| > |
|| > |
|| > |
|| > | What you need to do is create a custom Arraylist class (ie:
| MyArraylist)
|| > | which inherits Arraylist and overrides the indexof method as follows:
|| > |
|| > |
|| > |
|| > | Public Class MyArraylist
|| > |
|| > | Inherits ArrayList
|| > |
|| > | Public Overloads Overrides Function indexof(ByVal obj As
| Object)
|| > As
|| > | Integer
|| > |
|| > | If Me.Count = 0 Then Return -1
|| > |
|| > | For i As Integer = 0 To Me.Count - 1
|| > |
|| > | If Me.Item(i).Equals(obj) Then
|| > |
|| > | Return i
|| > |
|| > | End If
|| > |
|| > | Next
|| > |
|| > | Return -1
|| > |
|| > | End Function
|| > |
|| > | End Class
|| > |
|| > |
|| > |
|| > | This overridden indexof method does not care about the type of the
|| > parameter
|| > | passed to it. As long as the arraylist is not empty (me.count >0) it
|| > will
|| > | call the object.equals method and you will get the results you
expect.
|| > | Using this custom arraylist for your collection of objects gives you
a
|| > | tremendous amount of flexibility and greatly simplifies application
| code
|| > | that you need to grab a particular class instance out of the
| arraylist,
|| > | since you can "identify" the class instance you want by as many ways
| you
|| > can
|| > | think of (as long as they are properly handled in you custom
|| > object.equals
|| > | method!).
|| > |
|| > |
|| > |
|| > | Anyway, that's it. The above is based on my experimentation and works
|| > fine
|| > | for me... I hope this will help anybody else that is having issues
| with
|| > the
|| > | arraylist.indexof functionality.
|| > |
|| > |
|| > |
|| > | John
|| > |
|| > |
|| > |
|| > |
|| > |
|| > |
|| > |
|| > |
|| >
|| >
||
||
|
|
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top