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.
For LINQ to Objects, it's a stable quicksort that is used.
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.
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.
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.
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.
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; } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With