tags:

views:

66

answers:

3

I need to sort in memory lists of strings or numbers in ascending or descending order. However, the list can contain null values and all null values must appear after the numbers or strings.

That is the input data might be:

1, 100, null, 5, 32.3

The ascending result would be

1, 5, 32.3, 100, null

The descending list would be

100, 32.3, 5, 1, null

Any ideas on how to make this work?

+5  A: 

You can write your own comparer which delegates to an existing one for non-nulls, but always sorts nulls at the end. Something like this:

public class NullsLastComparer<T> : IComparer<T>
{
    private readonly IComparer<T> proxy;

    public NullsLastComparer(IComparer<T> proxy)
    {
        this.proxy = proxy;
    }

    public override int Compare(T first, T second)
    {
        if (first == null && second == null)
        {
            return 0;
        }
        if (first == null)
        {
            return 1;
        }
        if (second == null)
        {
            return -1;
        }
        return proxy.Compare(first, second);
    }
}

EDIT: A few issues with this approach:

Firstly, it doesn't play nicely with anonymous types; you might need a separate extension method to make that work well. Or use Ken's answer :)

More importantly, it violates the IComparer<T> contract, which specifies that nulls should be first. Now personally, I think this is a mistake in the IComparer<T> specification - it should perhaps define that the comparer should handle nulls, but it should not specify whether they come first or last... it makes requirements like this (which are perfectly reasonable) impossible to fulfil as cleanly as we might want, and has all kinds of awkward ramifications for things like a reversing comparer. You'd expect such a thing to completely reverse the ordering, but according to the spec it should still maintain nulls at the start :(

I don't think I've seen any .NET sorting implementations which actually rely on this, but it's definitely worth being aware of.

Jon Skeet
Isn't this a breach of contract with the .NET sorting implementations? I thought I've read somewhere that the sorting methods can assume null's come first... Or...?
Lasse V. Karlsen
@Lasse: It's a breach of the `IComparer<T>` interface, yes - I'll edit my post to mention this.
Jon Skeet
Yeah, but from what I remember from when reading about this, it sounded like the sorting methods could take shortcuts due to this contract, and thus not actually call the comparison method for some cases, since it "knew" already what the result was going to be. In other words, won't this produce funky results in some cases?
Lasse V. Karlsen
@Lasse: I can't say I've ever seen anything which takes "shortcuts" like that... it would mean *more* work rather than less, as it wouldn't be treating it as generally. If you've got any examples, I'd be interested to see them.
Jon Skeet
Won't type T need to be `IComparable` ? And the constructor should be dfeined as `public NullsLastComparer(IComparer<T> proxy)` without the T.
this. __curious_geek
@this.__curious_geek: No, you can implement `IComparer<T>` for types which don't have any natural comparison built in.
Jon Skeet
+12  A: 

I don't have a compiler in front of me to check, but I'm thinking something like:

x.OrderBy(i => i == null).ThenBy(i => i)
Ken
Nice... that's a really sweet approach. I'd vote for this more than once if I could...
Jon Skeet
A: 

As Jon said, you need to define your custom Comparer, implementing IComparer. Here's how your Compare method in the custom comparer would like that can keep null at the end.

    public int Compare(Object x, Object y)
    {
        int retVal = 0;

        IComparable valX = x as IComparable;
        IComparable valY = y as IComparable;

        if (valX == null && valY == null)
        {
            return 0;
        }

        if (valX == null)
        { 
            return 1; 
        }
        else if (valY == null)
        {
            return -1;
        }

        return valX.CompareTo(valY);
    }
this. __curious_geek