32

I have this class:

public class StatInfo
{
  public string contact;
  public DateTime date;
  public string action;
}

then I have a list of StatInfo, but I'm not sure how to sort it according to the date field. Should I use the sort method? Should I create my own?

var _allStatInfo = new List<StatInfo>();
// adding lots of stuff in it
_allStatInfo.SortByDate???

What is the best way of doing this without having to write tons of code (if possible)?

Thanks

pushkin
  • 9,575
  • 15
  • 51
  • 95
marcgg
  • 65,020
  • 52
  • 178
  • 231

6 Answers6

83

Using LINQ:

var sortedList = _allStatInfo.OrderBy(si => si.date).ToList();

Sorting the original list:

_allStatInfo.Sort(new Comparison<StatInfo>((x, y) => DateTime.Compare(x.date, y.date)));
Ben M
  • 22,262
  • 3
  • 67
  • 71
16

I see you've got the answer anyway, but...

  1. You can avoid some ugliness by just splitting the statement into two halves:

    Comparison<StatInfo> comparison = (x, y) => DateTime.Compare(x.date, y.date);
    _allStatInfo.Sort(comparison);
    

    You might want to consider just calling CompareTo directly, too:

    Comparison<StatInfo> comparison = (x, y) => x.date.CompareTo(y.date);
    _allStatInfo.Sort(comparison);
    
  2. You could create an IComparer<T> implementation using my ProjectionComparer class - it's part of MiscUtil, and I've included an uncommented version at the bottom of this answer. You'd then write:

    _allStatInfo.Sort(ProjectionComparer<StatInfo>.Create(x => x.date));
    
  3. Even if you're using .NET 2.0, you can still use LINQ by way of LINQBridge.

Here's the ProjectionComparer class required for the second answer. The first couple of classes are really just helpers to let generic type inference work better.

public static class ProjectionComparer
{
    public static ProjectionComparer<TSource, TKey> Create<TSource, TKey>
        (Func<TSource, TKey> projection)
    {
        return new ProjectionComparer<TSource, TKey>(projection);
    }

    public static ProjectionComparer<TSource, TKey> Create<TSource, TKey>
        (TSource ignored, Func<TSource, TKey> projection)
    {
        return new ProjectionComparer<TSource, TKey>(projection);
    }

}

public static class ProjectionComparer<TSource>
{
    public static ProjectionComparer<TSource, TKey> Create<TKey>
        (Func<TSource, TKey> projection)
    {
        return new ProjectionComparer<TSource, TKey>(projection);
    }
}

public class ProjectionComparer<TSource, TKey> : IComparer<TSource>
{
    readonly Func<TSource, TKey> projection;
    readonly IComparer<TKey> comparer;

    public ProjectionComparer(Func<TSource, TKey> projection)
        : this (projection, null)
    {
    }

    public ProjectionComparer(Func<TSource, TKey> projection,
                              IComparer<TKey> comparer)
    {
        projection.ThrowIfNull("projection");
        this.comparer = comparer ?? Comparer<TKey>.Default;
        this.projection = projection;
    }

    public int Compare(TSource x, TSource y)
    {
        // Don't want to project from nullity
        if (x==null && y==null)
        {
            return 0;
        }
        if (x==null)
        {
            return -1;
        }
        if (y==null)
        {
            return 1;
        }
        return comparer.Compare(projection(x), projection(y));
    }
}
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Oh thanks, just when I was saying that I didn't get an answer from you on a C# question :) I'll keep Ben's answer accepted but I upvoted yours and I'm sure it will be right below – marcgg Aug 19 '09 at 20:13
  • 1
    @Ben: I just don't like that many brackets if I can help it :) – Jon Skeet Aug 19 '09 at 20:38
  • Jon, in your `Compare` method, are the null checks really needed? That should be left to caller. – nawfal Oct 23 '13 at 12:36
  • @nawfal: I don't so. You want to be able to order by `foo => foo.Name` without having to do `foo => foo == null ? null : foo.Name` IMO. – Jon Skeet Oct 23 '13 at 12:37
  • @JonSkeet The example you provided could be written better, but that alone is not the case. What if we provide a method like `Compute(int, string)` as the projection, like `_allStatInfo.Sort(ProjectionComparer.Create(x => Compute(someInt, x)))` where a null for `x` is perfectly acceptable? Throwing a null reference exception or ignoring a null value should be at the discretion of the projection if this has to be fully generic.. – nawfal Oct 23 '13 at 13:33
  • @nawfal: You could always create an overload for that, should you wish to. But I think it's useful not to *have* to explicitly handle null for the common case. Note that this is also the approach that `Comparer.Default` takes. – Jon Skeet Oct 23 '13 at 13:40
  • @JonSkeet I did not what you meant by overload? Where does an overload fit in this scenario? – nawfal Oct 23 '13 at 15:02
  • I'm not sure if its appropriate to compare with `Comparer.Default` because `Default` compares `x` and `y` (of your example) as such, not a projection of `x` and `y`. In such a case it makes perfect sense to handle nulls, because the entire comparison logic is *within* the `IComparer` implementation. – nawfal Oct 23 '13 at 15:02
  • @nawfal: Well, it sort of does, in that the client doesn't get to work out how to handle a non-null value being compared with a null one - it's assumed to follow the original contract. When I talked about overloads, I actually meant you could add a different method *name* (not an overload, sorry) to the static class, so that the comparer could know whether or not to pass nulls along or handle them itself. – Jon Skeet Oct 23 '13 at 15:04
  • @JonSkeet I understood about overload part. But I think you haven't got me fully on my point on the nature of your `Compare` method. I understand the inconvenience of the caller if he has to check for null values beforehand, and agreed that's the majority of cases. But my point is there's no original contract here in case of projections. At least the contract doesnt cover *all the scenarios*. – nawfal Oct 23 '13 at 15:51
  • If `x == null` & `y != null`, **& if we've to compare `x` & `y`**, the original contract would be `x < y` as you put it, which is correct. But if we've to compare `projection(x)` & `projection(y)` where `x` & `y` can be null, then there is no original contract - it all depends on the context of the caller. This is where your attempt will fail. What I'm trying to say is there is a bug in your code, though it is rare. For eg, `new[] { "-1", null, "1" }.Sort(ProjectionComparer.Create(x => Convert.ToInt32(x)))` your code returns `[null, "-1", "1"]` but expected is `["-1", null, "1"]` – nawfal Oct 23 '13 at 15:52
  • @nawfal: It's not a bug - it's working as I designed it to work. It's just not what you expected it to do. We have different ideas about how it *should* work. Note that my implementation obeys the docs for `IComparer.Compare`, whereas other projections might not. After all, we *are* being asked to compare `x` and `y`, and the comparison is via a projection - your "expected" results violate the documented contract, even if that's what you actually want. – Jon Skeet Oct 23 '13 at 15:54
  • @JonSkeet I think then we have a difference of opinion about how a projection should work. My idea is when you say `_allStatInfo.Sort(ProjectionComparer.Create(x => x.date))` it should sort `_allStatInfo` based on the `date` property of the items in it irrespective of the item. Otherwise on what basis is something like `items.OrderBy(x => Guid.NewGuid())` written? But you say it should be respective of the item. Can you point me to a documentation about this? I'm sorry to have bothered you with this. I will make it a new question if it warrants.. – nawfal Oct 23 '13 at 16:02
  • @nawfal: Well the documentation of `IComparer.Compare` is here: http://msdn.microsoft.com/en-us/library/xh5ks3b3.aspx You could certainly argue that `IComparer` has been poorly designed on this front, but `ProjectionComparer` is meant to implement it, and I believe your suggestion would basically violate that contract. – Jon Skeet Oct 23 '13 at 16:54
  • @JonSkeet thanks for the link, certainly helpful. I would take that recommendation as a specific guideline when implementing `IComparer` on *null-throwable* implementations, like `foo.Name` (which is the norm usually) which makes sense. But in a generic implementation like yours I would avoid it. Or even better, may be we could take an `Expression>` as input and implement a crazy hybrid approach! :) – nawfal Oct 24 '13 at 09:22
  • @nawfal: What reason do you have for interpreting the contract that way? I see nothing in the interface documentation which says "You can ignore this if you like, specifically if you're delegating to a different comparer under the hood." It's simply part of the expected behaviour of the `Compare` method. Again, that may have been a bad idea - but it *is* the documented behaviour. – Jon Skeet Oct 24 '13 at 09:29
  • @JonSkeet I perfectly understand what you're saying. The reason tempting me to ignore the remark in the documentation are two: 1. Reading that part I felt MS was concerned more about `Compare` method throwing null reference exception. More like it was considered only for `foo.Name` scenarios. 2. I felt it was logically incorrect to ignore null values without knowing what the projection would do with that null value. Jon, on a second thought I think MS's idea makes sense too. Especially when you said: *After all, we are being asked to compare x and y, and the comparison is via a projection* – nawfal Oct 24 '13 at 09:37
5

To illustrate Robert C. Cartaino's answer:

public class StatInfo : IComparable<StatInfo>
{
    public string contact;
    public DateTime date;
    public string action;

    public int CompareTo(StatInfo value)
    {
        return this.date.CompareTo(value.date);
    }
}

var _allStatInfo = new List<StatInfo>();

// this now sorts by date
_allStatInfo.Sort();

Not the most general solution but good if you're only going to sort by date. And, as Robert said, you can still always override the default sort by passing an IComparer parameter to the sort method.

Dan Tao
  • 125,917
  • 54
  • 300
  • 447
  • thanks. It does not apply really to my situation since I'll only sort by date in this particular situation, but I'll keep this in mind for later – marcgg Aug 19 '09 at 20:15
  • @marcgg: The code in my answer makes sorting by date the default behavior. So if you are only sorting by date, I think that is exactly what you want. – Dan Tao Aug 19 '09 at 20:30
4

Use a lambda expression to map a pair to a comparison:

_allStatInfo.Sort((x, y) => x.date - y.date);
Cecil Has a Name
  • 4,962
  • 1
  • 29
  • 31
  • Thanks for the answer but I get an error: Cannot convert lambda expression to type System.Collections.Generic.IComparer' because it is not a delegate type – marcgg Aug 19 '09 at 18:38
2

it worked for me ُSorting array of custom type using delegate

// sort array by name
Array.Sort(users, delegate(User user1, User user2) 
           {
             return user1.Name.CompareTo(user2.Name);
           });
// write array (output: Betty23 Lisa25 Susan20)
foreach (User user in users) Console.Write(user.Name + user.Age + " ");
Mina
  • 2,167
  • 2
  • 25
  • 32
1

For a DateTime there shouldn't be a need to compare.

_allStatInfo.OrderyBy(d => d.date);

or

_allStatInfo.OrderByDescending(d => d.Date);
TruthStands
  • 93
  • 3
  • 9