Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use own IComparer<T> with Linq OrderBy

I have a generic

List<MyClass> 

where MyClass has a property InvoiceNumber which contains values such as:

200906/1
200906/2
..
200906/10
200906/11
200906/12

My list is bound to a

BindingList<T> 

which supports sorting with linq:

protected override void ApplySortCore(            PropertyDescriptor property, ListSortDirection direction) {      _sortProperty = property;     _sortDirection = direction;      var items = this.Items;      switch (direction)     {         case ListSortDirection.Ascending:             items = items.OrderByDescending(x => property.GetValue(x)).ToList();             break;         case ListSortDirection.Descending:             items = items.OrderByDescending(x => property.GetValue(x)).ToList();             break;     }      this.Items = items;  } 

However the default comparer sorts (as supposed) like this:

200906/1
200906/10
200906/11
200906/12
200906/2

which is nasty in this case.

Now I want to use my own IComparer<T> with this. It looks like this:

public class MyComparer : IComparer<Object> {      public int Compare(Object stringA, Object stringB)     {         String[] valueA = stringA.ToString().Split('/');         String[] valueB = stringB.ToString().Split('/');          if(valueA .Length != 2 || valueB .Length != 2)              return String.Compare(stringA.ToString(), stringB.ToString());          if (valueA[0] == valueB[0])          {           return String.Compare(valueA[1], valueB[1]);         }         else         {           return String.Compare(valueA[0], valueB[0]);         }      }  } 

and changed the ApplySortCore code to use this IComparer:

case ListSortDirection.Ascending:     MyComparer comparer = new MyComparer();     items = items.OrderByDescending(               x => property.GetValue(x), comparer).ToList();     break; 

When I debug my code, I see that MyComparer.Compare(object, object) is called multiple times and returns the right values (-1, 0, 1) for a compare method.

But my list is still sorted the "wrong" way. Am I missing something? I have no clue.

like image 460
Jürgen Steinblock Avatar asked Jun 12 '09 08:06

Jürgen Steinblock


People also ask

What algorithm does LINQ OrderBy use?

For LINQ to Objects, it's a stable quicksort that is used.

What is difference between OrderBy and ThenBy in LINQ?

Generally, ThenBy method is used with the OrderBy method. The OrderBy() Method, first sort the elements of the sequence or collection in ascending order after that ThenBy() method is used to again sort the result of OrderBy() method in ascending order.

What is OrderBy in LINQ?

Sorts the elements of a sequence in ascending order according to a key. OrderBy<TSource,TKey>(IEnumerable<TSource>, Func<TSource,TKey>, IComparer<TKey>) Sorts the elements of a sequence in ascending order by using a specified comparer.

Does OrderBy work on string?

OrderBy" function utilizes the default comparer for a string. That comparer is not necessarily going to return a sort order based on the ASCII code. For a list of all the different string comparers, see the article on MSDN.


2 Answers

Your comparer looks wrong to me. You're still just sorting in the default text ordering. Surely you want to be parsing the two numbers and sorting based on that:

public int Compare(Object stringA, Object stringB) {     string[] valueA = stringA.ToString().Split('/');     string[] valueB = stringB.ToString().Split('/');      if (valueA.Length != 2 || valueB.Length != 2)     {         stringA.ToString().CompareTo(stringB.ToString());     }      // Note: do error checking and consider i18n issues too :)     if (valueA[0] == valueB[0])      {         return int.Parse(valueA[1]).CompareTo(int.Parse(valueB[1]));     }     else     {         return int.Parse(valueA[0]).CompareTo(int.Parse(valueB[0]));     } } 

(Note that this doesn't sit well with your question stating that you've debugged through and verified that Compare is returning the right value - but I'm afraid I suspect human error on that front.)

Additionally, Sven's right - changing the value of items doesn't change your bound list at all. You should add:

this.Items = items; 

at the bottom of your method.

like image 176
Jon Skeet Avatar answered Sep 30 '22 18:09

Jon Skeet


I encountered the issue of general natural sorting and blogged the solution here:

Natural Sort Compare with Linq OrderBy()

public class NaturalSortComparer<T> : IComparer<string>, IDisposable {     private bool isAscending;      public NaturalSortComparer(bool inAscendingOrder = true)     {         this.isAscending = inAscendingOrder;     }      #region IComparer<string> Members      public int Compare(string x, string y)     {         throw new NotImplementedException();     }      #endregion      #region IComparer<string> Members      int IComparer<string>.Compare(string x, string y)     {         if (x == y)             return 0;          string[] x1, y1;          if (!table.TryGetValue(x, out x1))         {             x1 = Regex.Split(x.Replace(" ", ""), "([0-9]+)");             table.Add(x, x1);         }          if (!table.TryGetValue(y, out y1))         {             y1 = Regex.Split(y.Replace(" ", ""), "([0-9]+)");             table.Add(y, y1);         }          int returnVal;          for (int i = 0; i < x1.Length && i < y1.Length; i++)         {             if (x1[i] != y1[i])             {                 returnVal = PartCompare(x1[i], y1[i]);                 return isAscending ? returnVal : -returnVal;             }         }          if (y1.Length > x1.Length)         {             returnVal = 1;         }         else if (x1.Length > y1.Length)         {              returnVal = -1;          }         else         {             returnVal = 0;         }          return isAscending ? returnVal : -returnVal;     }      private static int PartCompare(string left, string right)     {         int x, y;         if (!int.TryParse(left, out x))             return left.CompareTo(right);          if (!int.TryParse(right, out y))             return left.CompareTo(right);          return x.CompareTo(y);     }      #endregion      private Dictionary<string, string[]> table = new Dictionary<string, string[]>();      public void Dispose()     {         table.Clear();         table = null;     } } 
like image 23
James McCormack Avatar answered Sep 30 '22 17:09

James McCormack