Use Own Icomparer<T> with Linq Orderby

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



Leave a reply



Submit