Use own IComparer T with Linq OrderBy
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.
Linq syntax for OrderBy with custom Comparer T
What is the syntax for the order-by expression in the second format?
It doesn't exist. From the orderby clause documentation:
You can also specify a custom comparer. However, it is only available by using method-based syntax.
How to use a custom comparer in the first format.
You wrote it correctly. You can pass the IComparer<T>
as you wrote.
Are there actual, formal names for the two Linq formats listed above?
Format 1 is called "Method-Based Syntax" (from previous link), and Format 2 is "Query Expression Syntax" (from here).
Passing an IComparer parameter to custom LINQ OrderBy extension method
I had to approach this differently.
I was trying to create a generic OrderBy
to be used with MvcContrib Grid, but passing the IComparer
to that custom OrderBy
expression did not work as I imagined it would work.
So I created this helper that receives a string in dot notation like Element1.Standard.Chapter.Manual.Name
and then returns an Expression<Func<T, string>>
:
public static Func<T, string> CreateSelectorExpression<T>(string propertyName) where T : class
{
ParameterExpression parameterExpression = Expression.Parameter(typeof(T));
Expression aggregateExpression = propertyName.Split('.').
Aggregate(parameterExpression as Expression, Expression.Property) as MemberExpression;
LambdaExpression exp = Expression.Lambda(aggregateExpression, parameterExpression);
return (Func<T, string>)exp.Compile();
}
This expression typed to T (in this case Divergence
object type) can then be passed (see func.Invoke
) to the standard LINQ OrderBy
operator where I can also pass the custom IComparer
AlphanumComparator
like this:
if (sort.Column.Contains("Index"))
{
var func = Helpers.ExtensionMethods.CreateSelectorExpression<Divergence>(sort.Column);
if (sort.Direction == SortDirection.Ascending)
{
return divergences.OrderBy(func, new AlphanumComparator());
}
else
{
return divergences.OrderByDescending(func, new AlphanumComparator());
}
}
This involved a little bit more work but solved the problem in a generic fashion the way I wanted it to be.
LINQ orderby vs IComparer
I would choose LINQ for two reasons.
- LINQ queries are generally shorter and easier to read.
- If you really do have a large number of elements, Linq also gives you the ability to scale out to multiple CPU cores by using PLinq, which might help you out significantly.
I would expect performance to be roughly similar for a single-threaded implementation, if you consider that the lambda expression in your OrderBy clause compiles to a function -- which is pretty much all you get by implementing IComparer anyway.
That being said, you might get more of a performance boost by changing your sort algorithm to be tailored to how your data is already sorted, rather than by changing your comparison method. But I'd be willing to bet my coffee this morning that OrderBy in your Linq statements uses an implementation of Quicksort, so it's probably pretty decent in the general case already.
How to implement the equivalent functionality to a custom IComparer for Linq to Entities OrderBy()?
If you want to use a custom IComparer
, you need to first ask yourself whether it's possible to do the sorting in the database. If it's not, just call AsEnumerable
on the sequence, and then sort it on the server.
If you can (and want to) sort it in the database, then you'll need to think through what conversions need to be made so that the sorting can happen. If you have a complex conversion, you could write it as a SQL function, and add it to your data context:
from e in context.Entities
let hours = context.Get32HourValue(e.Time)
orderby hours
select e
Using Custom IComparer to Order by Pointer value
public List<Student> GetOrderedStudents(List<Student> students)
{
Student[] reverseOrder = new Student[students.Count];
Student last = students.Single(s => s.FollowedBy == null);
reverseOrder[0] = last;
Student next = last;
for (var i = 1; i < students.Count; i++)
{
next = students.Single(s => s.FollowedBy == next.StudentID);
reverseOrder[i] = next;
}
return reverseOrder.Reverse().ToList();
}
LINQ Custom Sort
You need to use a comparison function, they are functions that from two instances of your type return an integer that return 0 if both are equals, a negative value if the first is less than the second and a positive value if the first is greater than the second.
MSDN has a nice table that is easier to follow than text (StackOverflow still doesn't support tables in 2014)
IComparer<T>
Most sort methods accept a custom comparer implementation of type IComparer<T>
you should create one encapsulating your custom rules for Group
:
class GroupComparer : IComparer<Group>
{
public int Compare(Group a, Group b)
{
if (a != null && b != null && (a.Id == 0 || b.Id == 0))
{
if (a.Id == b.Id)
{
// Mandatory as some sort algorithms require Compare(a, b) and Compare(b, a) to be consistent
return 0;
}
return a.Id == 0 ? -1 : 1;
}
if (a == null || b == null)
{
if (ReferenceEquals(a, b))
{
return 0;
}
return a == null ? -1 : 1;
}
return Comparer<string>.Default.Compare(a.Name, b.Name);
}
}
Usage:
items.OrderBy(_ => _, new GroupAuthorityComparer());
IComparable<T>
If it is the only way to compare Group
instances you should make it implement IComparable<T>
so that no aditional code is needed if anyone want to sort your class :
class Group : IComparable<Group>
{
...
public int CompareTo(Group b)
{
if (b != null && (Id == 0 || b.Id == 0))
{
if (Id == b.Id)
{
// Mandatory as some sort algorithms require Compare(a, b) and Compare(b, a) to be consistent
return 0;
}
return Id == 0 ? -1 : 1;
}
return Comparer<string>.Default.Compare(Name, b.Name);
}
}
Usage:
items.OrderBy(_ => _.Group);
The choice between one way or the other should be done depending on where this specific comparer is used: Is it the main ordering for this type of item or just the ordering that should be used in one specific case, for example only in some administrative view.
You can even go one level up and provide an IComparable<GroupAuthority>
implementation (It's easy once Group implement IComparable<Group>
):
class GroupAuthority : IComparable<GroupAuthority>
{
...
public int CompareTo(GroupAuthority b)
{
return Comparer<Group>.Default.Compare(Group, b.Group);
}
}
Usage:
items.OrderBy(_ => _);
The advantage of the last one is that it will be used automatically, so code like: GroupAuthoritys.ToList().Sort()
will do the correct thing out of the box.
Related Topics
How to Update a Table Using Oledb Parameters
Why Firefox Requires Geckodriver
Windows Application Startup Error Exception Code: 0Xe0434352
"Self Referencing Loop Detected" Exception with JSON.Net
Token Based Authentication in ASP.NET Core
Kanji Characters from Webclient HTML Different from Actual Kanji in Website
How to Filter All HTML Tags Except a Certain Whitelist
Parsing HTML Page with HTMLagilitypack
Can Itextsharp.Xmlworker Render Embedded Images
Is There a Jquery-Like CSS/HTML Selector That Can Be Used in C#
Conversion of System.Array to List
Unauthorised Webapi Call Returning Login Page Rather Than 401
How to Check for a Network Connection
Cancellation Token in Task Constructor: Why
Why Does Boolean.Tostring Output "True" and Not "True"