Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

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

89 views
Skip to first unread message

JohnR

unread,
Apr 26, 2005, 2:04:23 AM4/26/05
to
From reading the documentation, this should be a relatively easy thing. I
have an arraylist of custom class instances which I want to search with
an"indexof" where I'm passing an instance if the class where only the
"searched" property has a value. I expected to get the index into the
arraylist where I could then get the entire class instance. However, the
'indexof' is never calling my overloaded, overrides Equals method. Here is
the code snippet:

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load

Dim PeopleCollection As New ArrayList

Dim Customer As New Person
Customer.Age = 25
Customer.IsMale = "Yes"

PeopleCollection.Add(Customer)

Dim y As New Person
y.Age = 25
Dim x As Integer = PeopleCollection.IndexOf(y) 'this always
returns -1, meaning it didn't match anything
End Sub

Public Class Person
Private _IsMale As String
Private _age As Integer
Public Overloads Overrides Function equals(ByVal obj As Object) As Boolean
If obj Is Nothing Then Return False
If Me.GetType Is obj.GetType Then
Return Me.Age = CType(obj, Person).Age
End If
End Function
Public Property IsMale() As String
Get
Return _IsMale
End Get
Set(ByVal Value As String)
_IsMale = Value
End Set
End Property
Public Property Age() As Integer
Get
Return _age
End Get
Set(ByVal Value As Integer)
_age = Value
End Set
End Property
End Class

In this example I wanted to get the instance of the PERSON whose age is 25.
If I put a breakpoint in my overloads overrides equals method, it never
breaks. My EQUALS method is not being called.

Can anybody explain what is going on, and how I can get my indexof to do
what I need it to do?

Thanks, John


Larry Lard

unread,
Apr 26, 2005, 6:29:33 AM4/26/05
to

[snip]


>
> In this example I wanted to get the instance of the PERSON whose age
is 25.
> If I put a breakpoint in my overloads overrides equals method, it
never
> breaks. My EQUALS method is not being called.
>
> Can anybody explain what is going on, and how I can get my indexof to
do
> what I need it to do?

This is technically a feature not a bug (doesn't help I know), as the
behaviour is as documented. In the docs for ArrayList.IndexOf, you will
see it says:

>>
Remarks
...
This method determines equality by calling Object.Equals.
>>

which is why your class's Equals method is not being called. To get the
behaviour you want you have a number of choices which include these as
I think the most obvious two:

- Implement a strongly-typed ArrayList for storing solely Person
objects, that uses Person.Equals when doing an IndexOf operation;

- Use a different collection class, one that explicitly supports the
use of overriden Equals operators. For example, a Hashtable uses the
Equals implementation of the key objects, rather than always using
Object.Equals

If you need wither or both of these options explained more, feel free
to ask :)

--
Larry Lard
Replies to group please

JohnR

unread,
Apr 26, 2005, 10:39:14 AM4/26/05
to
Hi Larry,

> - Implement a strongly-typed ArrayList for storing solely
Person
> objects, that uses Person.Equals when doing an IndexOf
operation;

I'm not exactly sure what you mean...

Every entry into the arraylist was of type PERSON. But, still, the indexof
didn't call the object.equals in the Person class. I'm guessing that it is
calling a object.equals method that is in the ArrayList class, so I tried
to create a new class ARRAYLISTPERSON which inherits from arraylist. I then
put the overrides overloads function equals(byval obj as object) as boolean
in the arraylistperson class def'n and stored my PERSON instances in this
class rather than the regular arraylist class. Unfortunately, when the
indexof method was called, my Equals function was not called, so I'm still
at a loss.

Do you have a small code example of how to override the Object.equals
method of an arraylist.indexof?

Thanks, John


"Larry Lard" <larr...@hotmail.com> wrote in message
news:1114511373.8...@f14g2000cwb.googlegroups.com...

Larry Lard

unread,
Apr 26, 2005, 11:03:41 AM4/26/05
to

JohnR wrote:
> Hi Larry,
>
> > - Implement a strongly-typed ArrayList for storing
solely
> Person
> > objects, that uses Person.Equals when doing an
IndexOf
> operation;
>
> I'm not exactly sure what you mean...
>
> Every entry into the arraylist was of type PERSON. But, still, the
indexof
> didn't call the object.equals in the Person class. I'm guessing that
it is
> calling a object.equals method that is in the ArrayList class,

An ArrayList will *always* use Object.Equals (the Equals method defined
in the base class Object) when checking for equality in its IndexOf
method. That is just the way ArrayList is implemented.


> so I tried
> to create a new class ARRAYLISTPERSON which inherits from arraylist.
I then
> put the overrides overloads function equals(byval obj as object) as
boolean
> in the arraylistperson class def'n and stored my PERSON instances in
this
> class rather than the regular arraylist class. Unfortunately, when
the
> indexof method was called, my Equals function was not called, so I'm
still
> at a loss.

Supplying a new Equals method, wherever you do it, won't change
ArrayList's behaviour - it will always use Object.Equals.

>
> Do you have a small code example of how to override the
Object.equals
> method of an arraylist.indexof?

What you need to change the behaviour of is the *IndexOf* method. Here
is the general idea:

Public Class ArrayListOfPerson
Inherits ArrayList

Public Shadows Function IndexOf(ByVal p As Person) As Integer
Dim iterp As Person
Dim i As Integer

For i = 0 To Me.Count - 1
iterp = DirectCast(Me(i), Person)
If p.Equals(iterp) Then Return i
Next

Return -1
End Function
End Class

Usage example:

Dim al As ArrayList = New ArrayList

al.Add(New Person(10))
al.Add(New Person(5))

MsgBox(al.IndexOf(New Person(5)))
'this will show -1, because ArrayList uses Object.Equals to
compare
'while performing the IndexOf method
'and Object.Equals requires reference equivalence

Dim alp As ArrayListOfPerson = New ArrayListOfPerson

alp.Add(New Person(10))
alp.Add(New Person(5))

MsgBox(alp.IndexOf(New Person(5)))
'this will return 1, because ArrayListOfPerson uses
Person.Equals
'while performing IndexOf(As Person)


**** PLEASE NOTE **** that this lacks robustness in a number of ways,
and this is *not* how I would suggest implementing a strongly-typed
collection in anything larger than a toy (ie personal-use only)
application. The main problems are that: There is nothing to stop a
careless client of ArrayListOfPerson adding a non-Person; the Item()
property is still returned As Object so must be casted to Person; an
ArrayListOfPerson can be casted down to ArrayList and lose all its
custom behaviour. To reiterate, DON'T do custom collections in this way
in seroius apps! This is just an example! You can find many posts on
the correct ways to implement custom collections here in this group. Or
start a new thread and ask, as that is a separate topic :)

JohnR

unread,
Apr 26, 2005, 1:39:05 PM4/26/05
to
Hi Larry,

thanks for the quick reply. You have confirmed my idea of what needs to
be done... I have created my own custom arraylist as you suggested, and
created a few 'wrapper' functions to store/access/search/retrieve items
stored in it regardless of class.

it is a shame, however, that the arraylist.indexof class doesn't call the
overridden object.equals methods. Oh, well...

Regards,
John

"Larry Lard" <larr...@hotmail.com> wrote in message

news:1114527820.9...@z14g2000cwz.googlegroups.com...

Jay B. Harlow [MVP - Outlook]

unread,
Apr 26, 2005, 1:58:24 PM4/26/05
to
John,
Your code works as expected! As Larry pointed out, ArrayList.IndexOf uses
Object.Equals to match the items. Object.Equals is an overridable method, so
it behaves polymorphically across any type that overrides it. Your Person
type overrides Object.Equals. So ArrayList.IndexOf will call Person.Equals.

Trying your code as posted returns 0 in VB.NET 2003!

To see that Person.Equals is actually getting called, simply place a
breakpoint on the Person.Equals method before calling
PeopleCollection.IndexOf.

Hope this helps
Jay


"JohnR" <John...@hotmail.com> wrote in message
news:HRkbe.5251$WX.1041@trndny01...

Jay B. Harlow [MVP - Outlook]

unread,
Apr 26, 2005, 2:04:28 PM4/26/05
to
Larry,

| >>
| Remarks
| ...
| This method determines equality by calling Object.Equals.
| >>
|
| which is why your class's Equals method is not being called. To get the
| behaviour you want you have a number of choices which include these as
Err! Object.Equals is overridable, John overrode it. Overridable means the
method behaves polymorphically.

When ArrayList.IndexOf calls Object.Equals, because John's Person.Equals
overrides it, Person.Equals will actually be called!

Using Shadows is anti-polymorphic. When ArrayList.IndexOf calls
Object.Equals, if Person shadows Equals, then Object.Equals will be called &
not Person.Equals. Rarely do you want to use Shadows as they are
anti-polymorphic!

John's code as posted works as expected, I suspect there is another problem
going on.

Hope this helps
Jay


"Larry Lard" <larr...@hotmail.com> wrote in message
news:1114511373.8...@f14g2000cwb.googlegroups.com...
|

Larry Lard

unread,
Apr 26, 2005, 5:39:09 PM4/26/05
to

Jay B. Harlow [MVP - Outlook] wrote:
> Larry,
> | >>
> | Remarks
> | ...
> | This method determines equality by calling Object.Equals.
> | >>
> |
> | which is why your class's Equals method is not being called. To get
the
> | behaviour you want you have a number of choices which include these
as
> Err! Object.Equals is overridable, John overrode it. Overridable
means the
> method behaves polymorphically.
>
> When ArrayList.IndexOf calls Object.Equals, because John's
Person.Equals
> overrides it, Person.Equals will actually be called!
>
> Using Shadows is anti-polymorphic. When ArrayList.IndexOf calls
> Object.Equals, if Person shadows Equals, then Object.Equals will be
called &
> not Person.Equals. Rarely do you want to use Shadows as they are
> anti-polymorphic!
>
> John's code as posted works as expected, I suspect there is another
problem
> going on.

So it does. Guess I should have checked that before going off on one,
to avoid looking silly now. Oh well.

Tom Shelton

unread,
Apr 26, 2005, 6:14:39 PM4/26/05
to

IMHO, this is a bug... The problem is that it seems that the arraylist
Index of operator does something like the following (obviously aircode):

For Each obj As Object in List
value.Equals (obj)
Next

Which is backwards from what you would expect:

For Each obj As Object in List
obj.Equals (value)
Next

So, to get around this you need create a custom collection class that
overrides IndexOf... Here is my C# implementation of a class I did:

public int IndexOf(string value)
{
int index = -1;

// HACK: Get around strange arraylist.indexof method implementation...
for (int i = 0; i < this.InnerList.Count; i++)
{
if (this.InnerList[i].Equals(value))
{
index = i;
break;
}
}

return index;
}

The funny thing is that Mono did it the right way... But, broke it to
be like .NET in response to a post I made on the mono mailing list :)

--
Tom Shelton [MVP]

Jay B. Harlow [MVP - Outlook]

unread,
Apr 26, 2005, 6:50:41 PM4/26/05
to
Tom,

| IMHO, this is a bug... The problem is that it seems that the arraylist
| Index of operator does something like the following (obviously aircode):

I don't see there is a Bug in John's code. If you try his code it works as
expected in VS.NET 2003 (.NET 1.1)!


| For Each obj As Object in List
| value.Equals (obj)
| Next
|
| Which is backwards from what you would expect:
|
| For Each obj As Object in List
| obj.Equals (value)
| Next

Object.Equals is defined such that "if x.Equals(y) return the same value as
y.Equals(x)" so I don't really see a problem or bug in your sample either!

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfsystemobjectclassequalstopic1.asp

Hope this helps
Jay


"Tom Shelton" <t...@YOUKNOWTHEDRILLmtogden.com> wrote in message
news:uzQ2X1qS...@TK2MSFTNGP14.phx.gbl...

Tom Shelton

unread,
Apr 27, 2005, 12:49:51 AM4/27/05
to
On 2005-04-26, Jay B. Harlow [MVP - Outlook] <Jay_Har...@msn.com> wrote:
> Tom,
>| IMHO, this is a bug... The problem is that it seems that the arraylist
>| Index of operator does something like the following (obviously aircode):
>
> I don't see there is a Bug in John's code. If you try his code it works as
> expected in VS.NET 2003 (.NET 1.1)!
>
>

You're correct... His code does work as written. I failed to look at it
closely before responding. To quick on the draw :)

>| For Each obj As Object in List
>| value.Equals (obj)
>| Next
>|
>| Which is backwards from what you would expect:
>|
>| For Each obj As Object in List
>| obj.Equals (value)
>| Next
>
> Object.Equals is defined such that "if x.Equals(y) return the same value as
> y.Equals(x)" so I don't really see a problem or bug in your sample either!
>
> http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfsystemobjectclassequalstopic1.asp
>

I see that... I never noticed it, but that seems to be rather strange.
I have a class that I can compare to a string, but is not a string. So,
I want to overload equals to handle that case. But, then that
breaks that condition because:

Dim x As New TheClass
Dim y As String = somevalue

x.Equals (y) <> y.Equals (x)

So, really my implementation of equals wasn't to the spec. I don't like
that, condition. I did it that way because it simplified a lot of code I had to
write in the UI to manipulate those objects, so I don't think I'll change it
now.

--
Tom Shelton [MVP]

Jay B. Harlow [MVP - Outlook]

unread,
Apr 27, 2005, 10:16:43 AM4/27/05
to
Tom,

| I see that... I never noticed it, but that seems to be rather strange.
| I have a class that I can compare to a string, but is not a string. So,
| I want to overload equals to handle that case. But, then that
| breaks that condition because:
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...

I would not expect Person.Equals(String) to succeed.

| So, really my implementation of equals wasn't to the spec. I don't like
| that, condition. I did it that way because it simplified a lot of code I
had to
| write in the UI to manipulate those objects, so I don't think I'll change
it
| now.

I would consider putting a TODO or HACK comment, so anyone inheriting your
code knows you meant to do that...

Here is another link on implementing Equals:

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconEquals.asp

Alternatively I would consider adding an overloaded Equals method that
accepts a String, that does the String comparison & leave the Equals(Object)
method alone.

NOTE: I normally overload Equals to have both Equals(Object) &
Equals(MyType) in the MyType type. My Equals(Object) normally calls
Equals(MyType). Something like:

Public Class Person

Private Readonly m_name As String
Private Readonly m_id As String

Public Overloads Overrides Function Equals(obj As Object) As Boolean
If TypeOf obj Is Person Then
Return Equals(DirectCast(obj, Person))
Else
Return False
End If
End Function

Public Overloads Function Equals(other As Person) As Boolean
Return m_name = other.m_name AndAlso m_id = other.m_id
AndAlso...
End Function

End Class

Hope this helps
Jay


"Tom Shelton" <tshe...@YOUKNOWTHEDRILLcomcast.net> wrote in message
news:O%23%23aNSuS...@TK2MSFTNGP10.phx.gbl...

Tom Shelton

unread,
Apr 27, 2005, 2:09:05 PM4/27/05
to
In article <O5iuIPzS...@TK2MSFTNGP09.phx.gbl>, Jay B. Harlow [MVP - Outlook] wrote:
> Tom,
>| I see that... I never noticed it, but that seems to be rather strange.
>| I have a class that I can compare to a string, but is not a string. So,
>| I want to overload equals to handle that case. But, then that
>| breaks that condition because:
> 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...
>
> I would not expect Person.Equals(String) to succeed.
>
>| So, really my implementation of equals wasn't to the spec. I don't like
>| that, condition. I did it that way because it simplified a lot of code I
> had to
>| write in the UI to manipulate those objects, so I don't think I'll change
> it
>| now.
> I would consider putting a TODO or HACK comment, so anyone inheriting your
> code knows you meant to do that...
>
> Here is another link on implementing Equals:
>
> http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconEquals.asp
>
> Alternatively I would consider adding an overloaded Equals method that
> accepts a String, that does the String comparison & leave the Equals(Object)
> method alone.


I like the idea about the overloaded string version... I it would be a
very simple modification to my code, and the result would be the same.

--
Tom Shelton [MVP]

JohnR

unread,
Apr 28, 2005, 11:37:15 AM4/28/05
to
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


Jay B. Harlow [MVP - Outlook]

unread,
Apr 29, 2005, 10:36:01 AM4/29/05
to
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


"JohnR" <John...@hotmail.com> wrote in message

news:Lq7ce.2139$pc7.327@trndny05...

JohnR

unread,
Apr 30, 2005, 12:29:58 AM4/30/05
to
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

"Jay B. Harlow [MVP - Outlook]" <Jay_Har...@msn.com> wrote in message
news:OjiFKjMT...@TK2MSFTNGP15.phx.gbl...

Jay B. Harlow [MVP - Outlook]

unread,
Apr 30, 2005, 12:05:46 PM4/30/05
to
John,

| 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.
It sounds like you really want to use a HashTable, instead of an ArrayList.

The key to the hashtable would be JobCategory. The value of the hashtable
would be the Employee. Of course the downside would be needing

Rather then inheriting from ArrayList directly I would suggest you inherit
from DictionaryBase or CollectionBase or a class that derives from
DictionaryBase or CollectionBase instead. This allows better encapsulation &
more type safety...

If you can search by multiple criteria I would consider defining a single
Search method, that accepts a Criteria parameter. This Criteria parameter
could either be a Delegate that knew about what attributes to look for or it
could be a type hierarchy itself... In this case I would consider
overloading the IndexOf method, as the overloaded method would include the
criteria. The Criteria parameter would enable polymorphism & should
"simplify" your code.

Something like:

Public Class Person

Public Shared ReadOnly CompareName As PersonCriteria = AddressOf
DoCompareName
Public Shared ReadOnly CompareJobCategory As PersonCriteria =
AddressOf DoCompareJobCategory

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

Private Shared Function DoCompareName(ByVal item As Object, ByVal
value As Object) As Boolean
Return CompareName(DirectCast(item, Person), value)
End Function

Private Shared Function DoCompareName(ByVal item As Person, ByVal
value As Object) As Boolean
Return item.Name.Equals(value)
End Function

Private Shared Function DoCompareJobCategory(ByVal item As Object,
ByVal value As Object) As Boolean
Return CompareJobCategory(DirectCast(item, Person), value)
End Function

Private Shared Function DoCompareJobCategory(ByVal item As Person,
ByVal value As Object) As Boolean
Return item.JobCategory.Equals(value)
End Function

End Class

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(ByVal value As Object, ByVal criteria As
Criteria) As Integer
For index As Integer = 0 To Me.Count - 1
If criteria.Invoke(value, Me.InnerList(index)) Then


Return index
End If
Next
Return -1
End Function

Public Function IndexOf(ByVal value As Object, ByVal criteria As
PersonCriteria) As Integer
For index As Integer = 0 To Me.Count - 1
If criteria.Invoke(Me(index), value) Then


Return index
End If
Next
Return -1
End Function

End Class

Public Sub Main()
Dim list As New PersonCollection
list.Add(New Person("jay", "mvp"))
list.Add(New Person("john", "programmer"))

Dim index As Integer

index = list.IndexOf("jay", Person.CompareName)
index = list.IndexOf("programmer", Person.CompareJobCategory)

End Sub


The Criteria delegate is the most general, however it is the least type
safe. The PersonCriteria is Person specific, it is more type safe. I believe
you will need to wait for VS.NET 2005 (aka Whidbey, due out later in 2005
http://lab.msdn.microsoft.com/vs2005/), to get the most type safe with
Delegates. I'll see if I can come up with an example of the above with
Generics later... System.Array.Find is an example of using delegates
http://msdn2.microsoft.com/library/d9hy2xwa(en-us,vs.80).aspx

You only need to implement Criteria for all your classes or you need to
implement a *Type*Criteria for each of your classes. In other words its one
or the other, I just wanted to show both.

The Person.DoCompareName & Person.DoCompareJobCategory function know how to
compare a specific attribute of the Person object to a specific value.
Person.CompareName & Person.CompareJobCategory enable you to avoid needing
AddressOf all over the place.

The PersonCollection.IndexOf(Object, Criteria) &
PersonCollection.IndexOf(Object, PersonCriteria) calls the routine you give
it to compare an attribute of the current object with the requested value...

NOTE: Rather then return the IndexOf the item, I would consider finding the
Item itself.

Public Function Find(ByVal value As Object, ByVal criteria As
PersonCriteria) As Person
For Each item As Person In Me.InnerList
If criteria(item, value) Then
Return item
End If
Next
End Function

Dim foundPerson As Person = list.Find("jay", Person.CompareName)


My concern is that your current design is "Programming by Coincidence" that
overriding Object.Equals just happened to work, now that you are trying to
do ArrayList.IndexOf it doesn't work, so you are looking for a workaround to
get it to work, rather then finding a "cleaner" solution.
http://www.pragmaticprogrammer.com/ppbook/extracts/coincidence.html

Also its "by Coincidence" as you can tell the field to compare to, based on
the type of parameter to Equals. What happens when you have two attributes
that are strings that you want to compare to? Such as in my example?

| 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.

But they are not really that smart, as they don't play well with the
assumptions place on Equals. specifically the bullet point that states

Object.Equals is defined such that "if x.Equals(y) return the same value as

y.Equals(x)".

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfsystemobjectclassequalstopic1.asp

Also I suspect your Equals method is one long If, ElseIF, Else statement,
which is a good indication that you want to consider "Replace Conditional
with Polymorphism" refactoring.
http://www.refactoring.com/catalog/replaceConditionalWithPolymorphism.html

Hope this helps
Jay

Hope this helps
Jay

"JohnR" <John...@hotmail.com> wrote in message

news:aRDce.1147$KP.566@trndny02...

Jay B. Harlow [MVP - Outlook]

unread,
Apr 30, 2005, 12:43:12 PM4/30/05
to
Doh!

I seem to have missed the Delegates themselves:

Public Delegate Function Criteria(ByVal item As Object, ByVal value As
Object) As Boolean
Public Delegate Function PersonCriteria(ByVal item As Person, ByVal

value As Object) As Boolean

Hope this helps
Jay

"Jay B. Harlow [MVP - Outlook]" <Jay_Har...@msn.com> wrote in message

news:uSLuA6Z...@tk2msftngp13.phx.gbl...

Jay B. Harlow [MVP - Outlook]

unread,
Apr 30, 2005, 3:02:49 PM4/30/05
to
Doh!
It appears that I got this backwards:

| Public Function IndexOf(ByVal value As Object, ByVal criteria As
| Criteria) As Integer
| For index As Integer = 0 To Me.Count - 1

| *wrong* If criteria.Invoke(value, Me.InnerList(index)) Then

*correct* If criteria.Invoke(Me.InnerList(index), value) Then

| Return index
| End If
| Next
| Return -1
| End Function

Jay

"Jay B. Harlow [MVP - Outlook]" <Jay_Har...@msn.com> wrote in message

news:uSLuA6Z...@tk2msftngp13.phx.gbl...

Jay B. Harlow [MVP - Outlook]

unread,
Apr 30, 2005, 3:11:33 PM4/30/05
to
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

"JohnR" <John...@hotmail.com> wrote in message

news:aRDce.1147$KP.566@trndny02...

Jay B. Harlow [MVP - Outlook]

unread,
Apr 30, 2005, 4:10:14 PM4/30/05
to
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

"Jay B. Harlow [MVP - Outlook]" <Jay_Har...@msn.com> wrote in message

news:e$Z2MgbTF...@TK2MSFTNGP14.phx.gbl...

0 new messages