Sorting objects, that are in an ArrayList?

R

RCS

So I have an ArrayList that gets populated with objects like:

myAL.Add(new CustomObject(parm1,parm2));

I'm consuming this ArrayList from an ObjectDataSource and would like to have
this support sorting (because it's ultimately being consumed in a GridView).
I can't simply sort the ArrayList (because it only knows it's holding a
bunch of objects). So I need a way to sort the ArrayList, based on the
data - that is within the objects that it has loaded.

It seems like there must be some relatively simple way that I can make that
CustomObject() class sortable... any ideas??? TIA
 
W

Wiktor Zychla [C# MVP]

It seems like there must be some relatively simple way that I can make
that CustomObject() class sortable... any ideas??? TIA

you just implement IComparable for the default sort order or create an
external [or inner] class that implements IComparer for any other
[non-default] sort order.

Wiktor Zychla
 
R

Roland

Hi

I hope I understud your question in a correct way.

You need to use the IComparable Interface. -> YourClass : IComparable
In the method 'CompareTo(object x)' which you have to implement you can
compare anything you want and by returning a number less than zero you
indicate that 'This Instance' is smaller than x, by return 0 you
indicate that 'This Instance' is equal to x and a number bigger than
zero indicates that 'This Instance' is bigger than x.
But you are (can) defining the criteria in which case you return -1, 0
or 1

Finally you can call myArrayList.Sort(...);

see:
http://msdn.microsoft.com/library/d...ref/html/frlrfsystemicomparableclasstopic.asp
http://msdn.microsoft.com/library/d...frlrfsystemicomparableclasscomparetotopic.asp

Hope this helps
Roland
 
J

Jon Skeet [C# MVP]

RCS said:
So I have an ArrayList that gets populated with objects like:

myAL.Add(new CustomObject(parm1,parm2));

I'm consuming this ArrayList from an ObjectDataSource and would like to have
this support sorting (because it's ultimately being consumed in a GridView).
I can't simply sort the ArrayList (because it only knows it's holding a
bunch of objects). So I need a way to sort the ArrayList, based on the
data - that is within the objects that it has loaded.

It seems like there must be some relatively simple way that I can make that
CustomObject() class sortable... any ideas??? TIA

You can sort the ArrayList either by implementing IComparable within
the class itself, or by passing an IComparer which can compare two
CustomObjects into the call to ArrayList.Sort.
 
R

RCS

Thank you guys for the response - and I'm sort of with you guys on these
ideas (this was in the ballpark of what I was thinking, but I don't know how
to implement this). I don't understand *how* I would actually determine how
to sort my objects. Let me give a more thorough example...

I have a class called FileObject - which is a class that represents exactly
one file or directory, it has public get properties for filename, size,
extension, etc, etc.. Now, my other class FileSystemDataObject, populates an
arraylist, full of these FileObject objects (one, per file in a directory),
as it loops through System.IO.Directory.GetFiles(@"C:\") - for example.

So FileSystemDataObject has a populated ArrayList - that contains many
objects of type FileObject.

So, are you saying I should implement IComparable in the FileObject class?
If so - how does that help the containing ArrayList in sorting these
objects? I think I just need one more tiny push to grasp this.. thanks
much..
 
R

RCS

Does the caller (which I assume would be myArrayList.Sort) - does that pass
in another reference of my comparable class? In other words, let's say I
have a class called User that has FirstName and LastName properties.. and
User now implements IComparable.. and I now have to create public int
CompareTo(object obj)

Inside that function, is that another User object being sent in? And should
I do something like this?

User objUserFromOutside = (obj as User);
if ( this.FirstName > objUserFromOutside.FirstName)
return 1;
else
{
if ( this.FirstName == objUserFromOutside.FirstName)
return 0;
else
return -1;
}


If so - then how would I handle a sort of different fields (like lastname,
address, city, etc)???? Thanks again
 
C

Chris Dunaway

RCS said:
I don't understand *how* I would actually determine how
to sort my objects. Let me give a more thorough example...

You don't have to know how to sort your objects. All you need to do is
know how to compare two of them and return which one is smaller or
'less than' the other. The ArrayList will do the sorting and it will
call your CompareTo method which should return 1 if the first object is
greater than the second, 0 if they are equal, and -1 if the first is
less than the second.

Your CompareTo method can use any approach you want. The code you
posted earlier looked correct to me.
So, are you saying I should implement IComparable in the FileObject class?
If so - how does that help the containing ArrayList in sorting these
objects? I think I just need one more tiny push to grasp this.. thanks
much..

Because the ArrayList will take two of your objects and call the
CompareTo of one and pass in the other. Your CompareTo needs simply to
return 1, 0, or -1 which indicates which is greater.
 
J

Jon Skeet [C# MVP]

RCS said:
Thank you guys for the response - and I'm sort of with you guys on these
ideas (this was in the ballpark of what I was thinking, but I don't know how
to implement this). I don't understand *how* I would actually determine how
to sort my objects. Let me give a more thorough example...

I have a class called FileObject - which is a class that represents exactly
one file or directory, it has public get properties for filename, size,
extension, etc, etc.. Now, my other class FileSystemDataObject, populates an
arraylist, full of these FileObject objects (one, per file in a directory),
as it loops through System.IO.Directory.GetFiles(@"C:\") - for example.

So FileSystemDataObject has a populated ArrayList - that contains many
objects of type FileObject.

So, are you saying I should implement IComparable in the FileObject class?
If so - how does that help the containing ArrayList in sorting these
objects? I think I just need one more tiny push to grasp this.. thanks
much..

You could implement IComparable in the FileObject class, and then just
call Sort() on the ArrayList. Alternatively, you could implement
IComparer in another class to compare two FileObject instances, and
then call ArrayList.Sort(IComparer) with an instance of the IComparer
implementing class.
 
C

Chris Dunaway

Inside that function, is that another User object being sent in? And should
I do something like this?

User objUserFromOutside = (obj as User);
if ( this.FirstName > objUserFromOutside.FirstName)
return 1;
else
{
if ( this.FirstName == objUserFromOutside.FirstName)
return 0;
else
return -1;
}

Yes, that looks correct.
If so - then how would I handle a sort of different fields (like lastname,
address, city, etc)???? Thanks again

For that, you would probably have to use a class that implements
IComparer instead. Then add a constructor to that class that indicates
the field to compare against. Something like this (watch for typos):

Public Class MyComparer
Implements IComparer

Private _compareField As String

Public Sub New(ByVal comparefield As String)
_compareField = comparefield
End Sub

Public Function Compare(ByVal x As Object, ByVal y As Object) As
Integer Implements System.Collections.IComparer.Compare

Select Case _compareField
Case "FieldA"
'Compare Logic for fieldA
Case "FieldB"
'Compare Logic for fieldB
Case "FieldC"
'Compare Logic for fieldC
Case Else
'Default compare logic
End Select

End Function
End Class


Then you could call it like this:

Dim al As New ArrayList
'populate the arraylist here somehow

'Sort on FieldB
al.Sort(New MyComparer("FieldB"))
 
J

James Curran

When you implement IComparible (I'm assuming you are the creator of
FileObject and can add methods to it), you will wrote a CompareTo() method
for the class -- which ArrayList.Sort will call. Essentially(*), somewhere
deep inside Sort() are the lines:

IComparible left = myArrayList[x] as IComparible;
IComparible right = myArrayList[y] as IComparible;
if (left.CompareTo(right) > 0 )
{
// swap myArrayList[x] & myArrayList[y]
}

(*) I say "essentially", because the actual code is so optimized &
refactored, those actual lines aren't there at all, but that is in effect
what is happening.


--
Truth,
James Curran
[erstwhile VC++ MVP]

Home: www.noveltheory.com Work: www.njtheater.com
Blog: www.honestillusion.com Day Job: www.partsearch.com
RCS said:
Thank you guys for the response - and I'm sort of with you guys on these
ideas (this was in the ballpark of what I was thinking, but I don't know how
to implement this). I don't understand *how* I would actually determine how
to sort my objects. Let me give a more thorough example...

I have a class called FileObject - which is a class that represents exactly
one file or directory, it has public get properties for filename, size,
extension, etc, etc.. Now, my other class FileSystemDataObject, populates an
arraylist, full of these FileObject objects (one, per file in a directory),
as it loops through System.IO.Directory.GetFiles(@"C:\") - for example.

So FileSystemDataObject has a populated ArrayList - that contains many
objects of type FileObject.

So, are you saying I should implement IComparable in the FileObject class?
If so - how does that help the containing ArrayList in sorting these
objects? I think I just need one more tiny push to grasp this.. thanks
much..
 
C

Chris Dunaway

Chris said:
For that, you would probably have to use a class that implements
IComparer instead. Then add a constructor to that class that indicates
the field to compare against. Something like this (watch for typos):

And now that I think about it, you might do it by adding a static
property to your class that implements IComparable and do the same
thing. It would have to be static otherwise you would have to reset
the property on all instances every time you wanted to change the sort.
The calling code might be something like this:

MyComparableClass.SortField = "SomeField";
al.Sort();
 
R

RCS

Chris, John and James,

This is just what I needed - now I'm well underway!! this works just about
as I wanted it to. Thanks very, very much!!

and Chris, here's that code translated into C# and it works like a charm:

public class FileObjectComparer : IComparer
{
private string _compareField = "";

public FileObjectComparer(string comparefield)
{
_compareField = comparefield;
}

public int Compare(object x, object y)
{
switch (_compareField)
{
case "FileName":
{
return (x as FileObject).FileName.CompareTo((y as
FileObject).FileName);
break;
}
case "FileSize":
{
return (x as FileObject).FileSize.CompareTo((y as
FileObject).FileSize);
break;
}
default:
{
return (x as FileObject).FileName.CompareTo((y as
FileObject).FileName);
break;
}
}
}
}
 
R

Rick Lones

RCS said:
So I have an ArrayList that gets populated with objects like:

myAL.Add(new CustomObject(parm1,parm2));

I'm consuming this ArrayList from an ObjectDataSource and would like to have
this support sorting (because it's ultimately being consumed in a GridView).
I can't simply sort the ArrayList (because it only knows it's holding a
bunch of objects). So I need a way to sort the ArrayList, based on the
data - that is within the objects that it has loaded.

It seems like there must be some relatively simple way that I can make that
CustomObject() class sortable... any ideas??? TIA

Example:

public class foo : IComparable
{
string FirstName;
string LastName;
. . . etc;

public int CompareTo(object bar)
{
int highOrder;
if ((highOrder = this.LastName.CompareTo(bar.LastName)) != 0)
return highOrder;
else return this.FirstName.CompareTo(bar.FirstName);
}
}

If you have instances of foo, they can now be added as the key component of,
e.g., a SortedList and would (in this example) cause the list to sort in
(LastName, FirstName) order. The apparent recursion bottoms out right away
because string are natively comparable. You can extend this to create composite
indices of any type(s) you want as long as you can either define an ordering or
make use of a "natural" ordering for the type at hand.

HTH,
-rick-
 
J

John Vottero

You can take this a step further and create a Comparer class that uses
reflection to look at the properties of the object it's comparing to do the
compare. Then you get rid of the switch statement and use the same comparer
class for any object. Here's an example:

/// <summary>
/// The PropComparer class is a generic comparer. It takes a
PropertyDescriptor
/// and compares two of whatever type the descriptor points to.
/// </summary>
public class PropComparer : IComparer
{
private PropertyDescriptor sortProperty;
private ListSortDirection sortDirection;

/// <summary>
/// Creates a new PropComparer that will use the specified property
and direction.
/// </summary>
/// <param name="initProperty">The property to use.</param>
/// <param name="initDirection">The sort direction to use.</param>
public PropComparer(PropertyDescriptor initProperty,
ListSortDirection initDirection)
{
sortProperty = initProperty;
sortDirection = initDirection;
}

/// <summary>
/// Compares two objects based upon the passed properties.
/// </summary>
/// <param name="x">The property of object X.</param>
/// <param name="y">The property of object Y.</param>
/// <returns></returns>
public int Compare(object x, object y)
{
int result;
IComparable cmpIf;
object propertyX;
object propertyY;

//
// Get the actual objects that we are comparing
// These a basically fields from the X and Y objects
//
propertyX = sortProperty.GetValue(x);
propertyY = sortProperty.GetValue(y);

//
// Deal with one or both being null
//
if ((propertyX == null) && (propertyY == null))
{
result = 0;
}
else if (propertyX == null)
{
result = -1;
}
else if (propertyY == null)
{
result = 1;
}
else
{
//
// Get the IComparable interface
// This will throw an exception if the object isn't
comparable
//
cmpIf = (IComparable)propertyX;

//
// Compare X.Property and Y.Property
//
result = cmpIf.CompareTo(propertyY);
}

//
// If they are equal, see if we can compare the parent objects
//
if (result == 0)
{
//
// Get the IComparable interface from the X object
// This won't throw an exception if IComparable isn't
supported
//
cmpIf = x as IComparable;

if (cmpIf != null)
{
//
// Compare X and Y
//
result = cmpIf.CompareTo(y);
}
}

//
// If the direction is descending, reverse the sign
//
if (sortDirection == ListSortDirection.Descending)
{
result = -result;
}

return result;
}
}



and here is how you would use the PropComparer:

//
// Get a collection of property descriptors for your object
//
PropertyDescriptorCollection pdc =
TypeDescriptor.GetProperties(typeof(YourClass));

//
// Sort your list with the PropComparer
//
TheList.Sort(new PropComparer(pdc["FirstName"],
ListSortDirection.Ascending));


Reflection is one of the coolest features of .NET.

John Vottero
 
R

RCS

But one step better/easier - is that a GridView passes the "SortExpression"
field when it wants to sort ascending, then the SortExpression + " desc"
when it wants to sort descending. So since that is my consumer, I need only
work around that. So when I switch/case the SortExpression, I need only test
for that:

case "filename":
....
case "filename desc":
....

So in this particular case, the sort direction is handled - but that is
interesting code for things that don't handle sort direction the same way.
Thanks!


John Vottero said:
You can take this a step further and create a Comparer class that uses
reflection to look at the properties of the object it's comparing to do
the compare. Then you get rid of the switch statement and use the same
comparer class for any object. Here's an example:

/// <summary>
/// The PropComparer class is a generic comparer. It takes a
PropertyDescriptor
/// and compares two of whatever type the descriptor points to.
/// </summary>
public class PropComparer : IComparer
{
private PropertyDescriptor sortProperty;
private ListSortDirection sortDirection;

/// <summary>
/// Creates a new PropComparer that will use the specified property
and direction.
/// </summary>
/// <param name="initProperty">The property to use.</param>
/// <param name="initDirection">The sort direction to use.</param>
public PropComparer(PropertyDescriptor initProperty,
ListSortDirection initDirection)
{
sortProperty = initProperty;
sortDirection = initDirection;
}

/// <summary>
/// Compares two objects based upon the passed properties.
/// </summary>
/// <param name="x">The property of object X.</param>
/// <param name="y">The property of object Y.</param>
/// <returns></returns>
public int Compare(object x, object y)
{
int result;
IComparable cmpIf;
object propertyX;
object propertyY;

//
// Get the actual objects that we are comparing
// These a basically fields from the X and Y objects
//
propertyX = sortProperty.GetValue(x);
propertyY = sortProperty.GetValue(y);

//
// Deal with one or both being null
//
if ((propertyX == null) && (propertyY == null))
{
result = 0;
}
else if (propertyX == null)
{
result = -1;
}
else if (propertyY == null)
{
result = 1;
}
else
{
//
// Get the IComparable interface
// This will throw an exception if the object isn't
comparable
//
cmpIf = (IComparable)propertyX;

//
// Compare X.Property and Y.Property
//
result = cmpIf.CompareTo(propertyY);
}

//
// If they are equal, see if we can compare the parent objects
//
if (result == 0)
{
//
// Get the IComparable interface from the X object
// This won't throw an exception if IComparable isn't
supported
//
cmpIf = x as IComparable;

if (cmpIf != null)
{
//
// Compare X and Y
//
result = cmpIf.CompareTo(y);
}
}

//
// If the direction is descending, reverse the sign
//
if (sortDirection == ListSortDirection.Descending)
{
result = -result;
}

return result;
}
}



and here is how you would use the PropComparer:

//
// Get a collection of property descriptors for your object
//
PropertyDescriptorCollection pdc =
TypeDescriptor.GetProperties(typeof(YourClass));

//
// Sort your list with the PropComparer
//
TheList.Sort(new PropComparer(pdc["FirstName"],
ListSortDirection.Ascending));


Reflection is one of the coolest features of .NET.

John Vottero



RCS said:
Chris, John and James,

This is just what I needed - now I'm well underway!! this works just
about as I wanted it to. Thanks very, very much!!

and Chris, here's that code translated into C# and it works like a charm:

public class FileObjectComparer : IComparer
{
private string _compareField = "";

public FileObjectComparer(string comparefield)
{
_compareField = comparefield;
}

public int Compare(object x, object y)
{
switch (_compareField)
{
case "FileName":
{
return (x as FileObject).FileName.CompareTo((y as
FileObject).FileName);
break;
}
case "FileSize":
{
return (x as FileObject).FileSize.CompareTo((y as
FileObject).FileSize);
break;
}
default:
{
return (x as FileObject).FileName.CompareTo((y as
FileObject).FileName);
break;
}
}
}
}
 
J

John Vottero

It's not the sort direction that was the interesting part, it's using
reflection to see what properties the objects you're sorting have. You
eliminate the case statement and have a comparer that can work with any
class.

RCS said:
But one step better/easier - is that a GridView passes the
"SortExpression" field when it wants to sort ascending, then the
SortExpression + " desc" when it wants to sort descending. So since that
is my consumer, I need only work around that. So when I switch/case the
SortExpression, I need only test for that:

case "filename":
...
case "filename desc":
...

So in this particular case, the sort direction is handled - but that is
interesting code for things that don't handle sort direction the same way.
Thanks!


John Vottero said:
You can take this a step further and create a Comparer class that uses
reflection to look at the properties of the object it's comparing to do
the compare. Then you get rid of the switch statement and use the same
comparer class for any object. Here's an example:

/// <summary>
/// The PropComparer class is a generic comparer. It takes a
PropertyDescriptor
/// and compares two of whatever type the descriptor points to.
/// </summary>
public class PropComparer : IComparer
{
private PropertyDescriptor sortProperty;
private ListSortDirection sortDirection;

/// <summary>
/// Creates a new PropComparer that will use the specified
property and direction.
/// </summary>
/// <param name="initProperty">The property to use.</param>
/// <param name="initDirection">The sort direction to use.</param>
public PropComparer(PropertyDescriptor initProperty,
ListSortDirection initDirection)
{
sortProperty = initProperty;
sortDirection = initDirection;
}

/// <summary>
/// Compares two objects based upon the passed properties.
/// </summary>
/// <param name="x">The property of object X.</param>
/// <param name="y">The property of object Y.</param>
/// <returns></returns>
public int Compare(object x, object y)
{
int result;
IComparable cmpIf;
object propertyX;
object propertyY;

//
// Get the actual objects that we are comparing
// These a basically fields from the X and Y objects
//
propertyX = sortProperty.GetValue(x);
propertyY = sortProperty.GetValue(y);

//
// Deal with one or both being null
//
if ((propertyX == null) && (propertyY == null))
{
result = 0;
}
else if (propertyX == null)
{
result = -1;
}
else if (propertyY == null)
{
result = 1;
}
else
{
//
// Get the IComparable interface
// This will throw an exception if the object isn't
comparable
//
cmpIf = (IComparable)propertyX;

//
// Compare X.Property and Y.Property
//
result = cmpIf.CompareTo(propertyY);
}

//
// If they are equal, see if we can compare the parent
objects
//
if (result == 0)
{
//
// Get the IComparable interface from the X object
// This won't throw an exception if IComparable isn't
supported
//
cmpIf = x as IComparable;

if (cmpIf != null)
{
//
// Compare X and Y
//
result = cmpIf.CompareTo(y);
}
}

//
// If the direction is descending, reverse the sign
//
if (sortDirection == ListSortDirection.Descending)
{
result = -result;
}

return result;
}
}



and here is how you would use the PropComparer:

//
// Get a collection of property descriptors for your object
//
PropertyDescriptorCollection pdc =
TypeDescriptor.GetProperties(typeof(YourClass));

//
// Sort your list with the PropComparer
//
TheList.Sort(new PropComparer(pdc["FirstName"],
ListSortDirection.Ascending));


Reflection is one of the coolest features of .NET.

John Vottero



RCS said:
Chris, John and James,

This is just what I needed - now I'm well underway!! this works just
about as I wanted it to. Thanks very, very much!!

and Chris, here's that code translated into C# and it works like a
charm:

public class FileObjectComparer : IComparer
{
private string _compareField = "";

public FileObjectComparer(string comparefield)
{
_compareField = comparefield;
}

public int Compare(object x, object y)
{
switch (_compareField)
{
case "FileName":
{
return (x as FileObject).FileName.CompareTo((y as
FileObject).FileName);
break;
}
case "FileSize":
{
return (x as FileObject).FileSize.CompareTo((y as
FileObject).FileSize);
break;
}
default:
{
return (x as FileObject).FileName.CompareTo((y as
FileObject).FileName);
break;
}
}
}
}


Inside that function, is that another User object being sent in? And
should
I do something like this?

User objUserFromOutside = (obj as User);
if ( this.FirstName > objUserFromOutside.FirstName)
return 1;
else
{
if ( this.FirstName == objUserFromOutside.FirstName)
return 0;
else
return -1;
}

Yes, that looks correct.



If so - then how would I handle a sort of different fields (like
lastname,
address, city, etc)???? Thanks again


For that, you would probably have to use a class that implements
IComparer instead. Then add a constructor to that class that indicates
the field to compare against. Something like this (watch for typos):

Public Class MyComparer
Implements IComparer

Private _compareField As String

Public Sub New(ByVal comparefield As String)
_compareField = comparefield
End Sub

Public Function Compare(ByVal x As Object, ByVal y As Object) As
Integer Implements System.Collections.IComparer.Compare

Select Case _compareField
Case "FieldA"
'Compare Logic for fieldA
Case "FieldB"
'Compare Logic for fieldB
Case "FieldC"
'Compare Logic for fieldC
Case Else
'Default compare logic
End Select

End Function
End Class


Then you could call it like this:

Dim al As New ArrayList
'populate the arraylist here somehow

'Sort on FieldB
al.Sort(New MyComparer("FieldB"))
 

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