Trying to simplify my List Collection Sort

T

tshad

I have dataGrid that I am filling from a List Collection and need to sort it
by the various columns.

So I need to be able to sort the Collection and found that you have to set
up your own sorting functions to make it work. I have build the following 3
sorting
functions for the 3 columns and have to call each one specifically.

I am trying to find a way to cut simplify the functions and calls to make
this a more
general class. At the moment, if I add another column I need to add another
sorting method
and another call from my switch.

Here is the Class and Sorts
**********************************************
using System;
using System.Collections.Generic;

namespace Irez
{
[Serializable()]
public class DisplayAmount : IComparable<DisplayAmount>
{
public DisplayAmount()
{
}
string mstrDisplayName = "";
double mdblDisplayValue = 0;
string mstrTransactionDescription = "";

public string DisplayName
{
get{ return mstrDisplayName; }
set{ mstrDisplayName = value;}
}

public double DisplayValue
{
get{ return mdblDisplayValue;}
set{ mdblDisplayValue = value;}
}

public string TransactionDescription
{
get{ return mstrTransactionDescription;}
set{ mstrTransactionDescription = value;}
}

// Sorts
public static int CompareName(DisplayAmount c1, DisplayAmount c2)
{
return c1.DisplayName.CompareTo(c2.DisplayName);
}

public static int CompareValue(DisplayAmount c1, DisplayAmount c2)
{
return c1.DisplayValue.CompareTo(c2.DisplayValue);
}

public static int CompareTransactionDescription(DisplayAmount c1,
DisplayAmount c2)
{
return
c1.TransactionDescription.CompareTo(c2.TransactionDescription);
}
}

// Define Collections here
public class DisplayAmountCollection : List<DisplayAmount> { }
}
************************************************************

To sort the collection I call the following function with the Collection,
the name of the column to sort and
the direction.

*************************************************************
protected void SortList(ref DisplayAmountCollection ocol, string
sortExpression, string sortDirection)
{
Comparison<DisplayAmount> oColDel = null;

switch (sortExpression)
{
case "DisplayName":
oColDel = new
Comparison<DisplayAmount>(DisplayAmount.CompareName);
ocol.Sort(oColDel);
break;
case "DisplayValue":
oColDel = new
Comparison<DisplayAmount>(DisplayAmount.CompareValue);
ocol.Sort(oColDel);
break;
case "TransactionDescription":
oColDel = new
Comparison<DisplayAmount>(DisplayAmount.CompareTransactionDescription);
ocol.Sort(oColDel);
break;
}
if (sortDirection == "DESC") ocol.Reverse();
}
*************************************************************

I'd like to see if I can simplify this if possible. If I have 10 columns, I
would have 10 case statements.

Also, I was trying to do a similar thing with my "Comparison<DisplayAmount>"
as I do with my "DisplayListCollection".

I don't have to do:

List<DisplayAmount> d1 = null;

in my code. I put this in my class:

public class DisplayAmountCollection : List<DisplayAmount> { }

and then call it in my program as:

DisplayAmountCollection dc = null;

But I can't seem to the same with my Comparison<t> statement without getting
an error. If I do the following:

public class DisplayAmountSort : Comparison<DisplayAmount> { }

I get the error:

cannot derive from sealed type 'System.Comparison<DisplayAmount>'

Why can I do it with List but not with Comparison?

Thanks,

Tom
 
M

Marc Gravell

First - you don't need the "ref", since you aren't reallocating the
list.
Well, you could look at a PropertyDescriptor based approach here (both
single and multi are mentioned):

http://groups.google.com/group/micr...read/thread/d76e639a64e77097/1e23312d4c9ae353

Can I also note that this would be a trivial implementation using LINQ
in .NET 3.5 and C# 3; the standard LINQ support returns a *copy* of
the list, but it is trivial to use lambdas to make such constructs.
Here is a trivial implementation (only supporting single sort) -
although much more is possible. I've extended List<T> here to use the
existing Sort, but in theory you could work on IList<T> if you wrote
the sort algorithm too...

using System;
using System.Collections.Generic;

class SomeEntity
{
public string Name { get; set; }
public DateTime DateOfBirth { get; set; }
public int Priority {get;set;}
}
class Program
{
static void Main()
{
List<SomeEntity> list = new List<SomeEntity>();
// name ascending, case insensitive
list.Sort(x => x.Name,
StringComparer.CurrentCultureIgnoreCase, false);
// priority descending
list.Sort(x => x.Priority, true);
}
}


/*
The rest is support code, only written once
*/

static class ListExt
{
public static void Sort<T, TValue>(this List<T> list, Func<T,
TValue> selector, IComparer<TValue> comparer, bool descending)
{
list.Sort(new SelectorComparer<T, TValue>(selector, comparer,
descending));
}
public static void Sort<T, TValue>(this List<T> list, Func<T,
TValue> selector, bool descending)
{
list.Sort(new SelectorComparer<T, TValue>(selector, null,
descending));
}
}
/// <summary>
/// Comparer to sort elements of T based on a projection
/// </summary>
/// <typeparam name="T">Element type</typeparam>
/// <typeparam name="TKey">The projected type (usually a property)</
typeparam>
internal class SelectorComparer<T, TKey> : IComparer<T>
{
private readonly Func<T, TKey> selector;
private readonly IComparer<TKey> comparer;
private readonly bool descending;

/// <summary>
/// Create a new comparer based on a projection
/// </summary>
/// <param name="selector">The projection function to apply to
obtain the key to compare</param>
/// <param name="comparer">The comparer used to compare keys
(defaults if null)</param>
/// <param name="descending">Should the order be considered
ascending or descending?</param>
public SelectorComparer(
Func<T, TKey> selector,
IComparer<TKey> comparer,
bool descending)
{
if (selector == null) throw new
ArgumentNullException("selector");
if (comparer == null) comparer = Comparer<TKey>.Default;
this.selector = selector;
this.comparer = comparer;
this.descending = descending;
}

int IComparer<T>.Compare(T x, T y)
{
return descending
? comparer.Compare(selector(y), selector(x))
: comparer.Compare(selector(x), selector(y));
}
}
 
M

Marc Gravell

I forgot to stress - the code sample was for doing an in-place sort.
If you can work with a copy, then simply use:
list.OrderBy(...).ThenBy(...).ThenByDescending(...).ToList();
etc and you're sorted.

Marc
 
I

Ignacio Machin \( .NET/ C# MVP \)

Hi,


If you are always going to sort by using only one column you can use a code
I created a time ago just for this:
http://groups.google.com/group/micr...acio+machin"+sort+reflection#6297990b9dcd65e2

The good part about it is that the "sorted" class do not have knowledge of
it. you can sort a collection of ANY type.

You might ahve to convert it to use IComparer<T> as the code was created in
1.1

--
Ignacio Machin
http://www.laceupsolutions.com
Mobile & warehouse Solutions.
tshad said:
I have dataGrid that I am filling from a List Collection and need to sort
it
by the various columns.

So I need to be able to sort the Collection and found that you have to set
up your own sorting functions to make it work. I have build the following
3 sorting
functions for the 3 columns and have to call each one specifically.

I am trying to find a way to cut simplify the functions and calls to make
this a more
general class. At the moment, if I add another column I need to add
another sorting method
and another call from my switch.

Here is the Class and Sorts
**********************************************
using System;
using System.Collections.Generic;

namespace Irez
{
[Serializable()]
public class DisplayAmount : IComparable<DisplayAmount>
{
public DisplayAmount()
{
}
string mstrDisplayName = "";
double mdblDisplayValue = 0;
string mstrTransactionDescription = "";

public string DisplayName
{
get{ return mstrDisplayName; }
set{ mstrDisplayName = value;}
}

public double DisplayValue
{
get{ return mdblDisplayValue;}
set{ mdblDisplayValue = value;}
}

public string TransactionDescription
{
get{ return mstrTransactionDescription;}
set{ mstrTransactionDescription = value;}
}

// Sorts
public static int CompareName(DisplayAmount c1, DisplayAmount c2)
{
return c1.DisplayName.CompareTo(c2.DisplayName);
}

public static int CompareValue(DisplayAmount c1, DisplayAmount c2)
{
return c1.DisplayValue.CompareTo(c2.DisplayValue);
}

public static int CompareTransactionDescription(DisplayAmount c1,
DisplayAmount c2)
{
return
c1.TransactionDescription.CompareTo(c2.TransactionDescription);
}
}

// Define Collections here
public class DisplayAmountCollection : List<DisplayAmount> { }
}
************************************************************

To sort the collection I call the following function with the Collection,
the name of the column to sort and
the direction.

*************************************************************
protected void SortList(ref DisplayAmountCollection ocol, string
sortExpression, string sortDirection)
{
Comparison<DisplayAmount> oColDel = null;

switch (sortExpression)
{
case "DisplayName":
oColDel = new
Comparison<DisplayAmount>(DisplayAmount.CompareName);
ocol.Sort(oColDel);
break;
case "DisplayValue":
oColDel = new
Comparison<DisplayAmount>(DisplayAmount.CompareValue);
ocol.Sort(oColDel);
break;
case "TransactionDescription":
oColDel = new
Comparison<DisplayAmount>(DisplayAmount.CompareTransactionDescription);
ocol.Sort(oColDel);
break;
}
if (sortDirection == "DESC") ocol.Reverse();
}
*************************************************************

I'd like to see if I can simplify this if possible. If I have 10 columns,
I would have 10 case statements.

Also, I was trying to do a similar thing with my
"Comparison<DisplayAmount>" as I do with my "DisplayListCollection".

I don't have to do:

List<DisplayAmount> d1 = null;

in my code. I put this in my class:

public class DisplayAmountCollection : List<DisplayAmount> { }

and then call it in my program as:

DisplayAmountCollection dc = null;

But I can't seem to the same with my Comparison<t> statement without
getting an error. If I do the following:

public class DisplayAmountSort : Comparison<DisplayAmount> { }

I get the error:

cannot derive from sealed type 'System.Comparison<DisplayAmount>'

Why can I do it with List but not with Comparison?

Thanks,

Tom
 
T

tshad

I was a little confused at some of the code. I wasn't sure which was
supposed to be replaced and which was supposed to be used verbatim (below)

I want to just get a sample program working and then study how it works.

I created a small console program and just put your ListExt and
SelectorComparer in and got a bunch of errors. I assume I put the classes
in the wrong place or am missing a reference or 2. Or is it maybe because I
am using VS 2005?

********************************************************
using System;
using System.Collections.Generic;
using System.Text;

namespace ConsoleApplication1
{
static class ListExt
{
public static void Sort<T, TValue>(this List<T> list, Func<T,TValue>
selector, IComparer<TValue> comparer, bool descending)
{
list.Sort(new SelectorComparer<T, TValue>(selector,
comparer,descending));
}
public static void Sort<T, TValue>(this List<T> list, Func<T,TValue>
selector, bool descending)
{
list.Sort(new SelectorComparer<T, TValue>(selector, null,
descending));
}
}
/// <summary>
/// Comparer to sort elements of T based on a projection
/// </summary>
/// <typeparam name="T">Element type</typeparam>
/// <typeparam name="TKey">The projected type (usually a
property)</typeparam>
internal class SelectorComparer<T, TKey> : IComparer<T>
{
private readonly Func<T, TKey> selector;
private readonly IComparer<TKey> comparer;
private readonly bool descending;

/// <summary>
/// Create a new comparer based on a projection
/// </summary>
/// <param name="selector">The projection function to apply to obtain
the key to compare</param>
/// <param name="comparer">The comparer used to compare keys (defaults
if null)</param>
/// <param name="descending">Should the order be considered ascending or
descending?</param>
public SelectorComparer(
Func<T, TKey> selector,
IComparer<TKey> comparer,
bool descending)
{
if (selector == null) throw new ArgumentNullException("selector");
if (comparer == null) comparer = Comparer<TKey>.Default;
this.selector = selector;
this.comparer = comparer;
this.descending = descending;
}

int IComparer<T>.Compare(T x, T y)
{
return descending
? comparer.Compare(selector(y), selector(x))
: comparer.Compare(selector(x), selector(y));
}
}
class Program
{
static void Main(string[] args)
{
}
}
}
**************************************************************

I got the following errors:
15 Type or namespace definition, or end-of-file expected
1 Type expected
4 Invalid token '>' in class, struct, or interface member declaration
3 Invalid token ',' in class, struct, or interface member declaration
5 Invalid token ',' in class, struct, or interface member declaration
10 Invalid token ',' in class, struct, or interface member declaration
11 Invalid token ',' in class, struct, or interface member declaration
12 Invalid token ')' in class, struct, or interface member declaration
8 Invalid token '(' in class, struct, or interface member declaration
9 Invalid token '(' in class, struct, or interface member declaration
6 Identifier expected, 'bool' is a keyword
13 Expected class, delegate, enum, interface, or struct
14 Expected class, delegate, enum, interface, or struct
2 ; expected
7 ; expected

I am confused on some of these errors. The first one is on the close brace
before "Summary". The 2nd error is on the "this" of the Sort declaration.
The "Expected class" ones are on the 2nd void and the SelectorComparer (even
though it is there).

I assume some of the errors are because I am missing something and happen
because of previous errors.

Marc Gravell said:
First - you don't need the "ref", since you aren't reallocating the
list.

But if I am passing it with "ref", doesn't that say to pass it by reference
and not value?
Well, you could look at a PropertyDescriptor based approach here (both
single and multi are mentioned):

http://groups.google.com/group/micr...read/thread/d76e639a64e77097/1e23312d4c9ae353

Can I also note that this would be a trivial implementation using LINQ
in .NET 3.5 and C# 3; the standard LINQ support returns a *copy* of
the list, but it is trivial to use lambdas to make such constructs.
Here is a trivial implementation (only supporting single sort) -
although much more is possible. I've extended List<T> here to use the
existing Sort, but in theory you could work on IList<T> if you wrote
the sort algorithm too...

using System;
using System.Collections.Generic;

class SomeEntity
{
public string Name { get; set; }
public DateTime DateOfBirth { get; set; }
public int Priority {get;set;}
}
class Program
{
static void Main()
{
List<SomeEntity> list = new List<SomeEntity>();
// name ascending, case insensitive
list.Sort(x => x.Name,
StringComparer.CurrentCultureIgnoreCase, false);
// priority descending
list.Sort(x => x.Priority, true);
}
}

I don't quite understand what the "x => x.Name" is. What is "x"?

Do you put the code in below exactly as written or do you need to replace
the <T, TValue> or <TKey> with something?

Thanks,

Tom
 
M

Marc Gravell

I wasn't sure which was
supposed to be replaced and which was supposed to be used verbatim (below)

The bit at the top (above "The rest is support code, only written
once") would vary with your scenario; the bottom bit would remain
verbatim.

....
and got a bunch of errors.
Or is it maybe because I
am using VS 2005?

Hence the caveat "using LINQ in .NET 3.5 and C# 3".
For C# 2 (VS 2005) I would probably stick with the other cited links -
but hey! VS 2008 (including express) is here, and it might make your
life easier. This code sample uses syntax and constructs only
available in C# 3 (although in *essence* it can be made to use C#
3, .NET 2.0 if you replace "Func" with "Converter", but it might not
be worth it)
I don't quite understand what the "x => x.Name" is. What is "x"?

Basically, it is short hand; if you are familiar with anonymous
methods, an equivalent statement (using Converter) would be:

"(Converter<SomeEntity, string>) delegate(SomeEntity x) { return
x.Name; }"

Basically, rather than giving it a column name (or
PropertyDescriptor), we are giving it a well-defined function that it
can call on each instance (as SomeEntity) to get the value for that
item (as string); x is simply the name inside the function.
But as you can see, the above is not an attractive way of saying "sort
by Name"; however, "x => x.Name" probably *is* a fairly acceptable way
of telling it what property we care about.

Marc
 
M

Marc Gravell

But if I am passing it with "ref", doesn't that say to pass it by reference
and not value?

This gets tricky to explain... the short version is that List<T> (etc)
is a reference-type, which means that even without "ref" all you are
passing is the address of the list (on the managed heap). If you use
"ref" here, then instead of passing the address of the list, it passes
the address of the *variable* in the calling code (typically on the
stack, although it could be a field etc). The only things "ref" gives
you here is the ability to update the variable directly and have that
reassignment propegate to the calling method - but you don't need to
do that. Neither way (alone) causes the list to clone itself.

Jon Skeet puts it better: http://www.pobox.com/~skeet/csharp/parameters.html

Marc
 
T

tshad

Marc Gravell said:
The bit at the top (above "The rest is support code, only written
once") would vary with your scenario; the bottom bit would remain
verbatim.

...

Hence the caveat "using LINQ in .NET 3.5 and C# 3".
For C# 2 (VS 2005) I would probably stick with the other cited links -

Which cited links?
but hey! VS 2008 (including express) is here, and it might make your
life easier. This code sample uses syntax and constructs only
available in C# 3 (although in *essence* it can be made to use C#
3, .NET 2.0 if you replace "Func" with "Converter", but it might not
be worth it)

Unfortunately, we are not using 2008 yet. And when we do, we still will
have projects in 2005, so I would like to find a solution in both.
Basically, it is short hand; if you are familiar with anonymous
methods, an equivalent statement (using Converter) would be:

I'm not, yet. I was hoping to use this to get more familier with it.
 

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