19

A sort of:

Documenti = Documenti
    .OrderBy(o => string.IsNullOrEmpty(o.Note))
    .ThenBy(o => Int32.TryParse(o.Note))
    .ToList();

That will "ignore" (not order, putting at the end) if o.Note is "" or not an int.

How can I do it?

markzzz
  • 47,390
  • 120
  • 299
  • 507

5 Answers5

33

Everyone who uses C#7 or newer scroll to the bottom, everyone else can read the original answer:


Yes, you can, if you pass the correct parameters to int.TryParse. Both overloads take the int as out-parameter and initialize it inside with the parsed value. So like this:

int note;
Documenti = Documenti
    .OrderBy(o => string.IsNullOrEmpty(o.Note))
    .ThenBy(o => Int32.TryParse(o.Note, out note)) 
    .ToList();

The clean approach is using a method that parses to int and returns int? if unparseable:

public static int? TryGetInt(this string item)
{
    int i;
    bool success = int.TryParse(item, out i);
    return success ? (int?)i : (int?)null;
}

Now you can use this query(OrderByDescending because true is "greater" than false):

Documenti = Documenti.OrderByDescending(d => d.Note.TryGetInt().HasValue).ToList();

It's cleaner than using a local variable that is used in int.TryParse as out parameter.

Eric Lippert commented another answer of me where he gives an example when it might hurt:

C# LINQ: How is string("[1, 2, 3]") parsed as an array?


Update, this has changed with C#7. Now you can declare the variable directly where you use out parameters:

Documenti = Documenti
.OrderBy(o => string.IsNullOrEmpty(o.Note))
.ThenBy(o => Int32.TryParse(o.Note, out int note)) 
.ToList();
Tim Schmelter
  • 450,073
  • 74
  • 686
  • 939
4
Documenti = Documenti.OrderBy(o =>
        int.TryParse(o.Note, out int val)
            ? val
            : int.MaxValue /* or int.MinValue */
    ).ToList();

Note: Toggling between int.MaxValue and int.MinValue will either put the empty values at the front or the end of the list.

EDIT: 2020-02-07 Using an inline out variable which was introduced in C# 7

dana
  • 17,267
  • 6
  • 64
  • 88
  • 1
    @TimSchmelter The thing that isn't guaranteed to work is referencing the same `dummy` variable in another delegate, such as `strings.Where(s => int.TryParse(s, out dummy)).Select(s => dummy)`. That isn't what this answer has, there is no problem here. –  May 17 '13 at 16:27
  • 1
    I agree it is probably best practice to move the temp variable inside the delegate and is pretty easy to do. I actually modified my answer to do this, but then decided to roll it back since there seems to be a good discussion on whether it is kosher to use a variable like this. – dana May 17 '13 at 16:49
  • @Servy: i have only just seen these old comments. I have already edited my answer above to link to [a comment of E. Lippert](http://stackoverflow.com/questions/28988523/c-sharp-linq-how-is-string1-2-3-parsed-as-an-array#comment46233107_28988762) where he shows that not a implementation detail might change but that using `int.TryParse` in a LINQ query can cause other undesired effects. – Tim Schmelter Jun 30 '15 at 09:33
  • @TimSchmelter That's showing that it's confusing, hard to read/understand, poor practice, easy for a future developer to accidentally break if they're refactoring the code, etc. None of that makes it relying on implementation details. I absolutely agree with using Eric's approach of wrapping this functionality in a method to hide the variable mutation behind a black box, it's simply your explanation of why that's important isn't correct. – Servy Jun 30 '15 at 13:38
3

You can actually put much more complex logic in the lambda expression:

List<Doc> Documenti = new List<Doc>() {
        new Doc(""),
        new Doc("1"),
        new Doc("-4"),
        new Doc(null) };

Documenti = Documenti.OrderBy(o => string.IsNullOrEmpty(o.Note)).ThenBy(o => 
{
    int result;
    if (Int32.TryParse(o.Note, out result))
    {
        return result;
    } else {
        return Int32.MaxValue;
    }
}).ToList();

foreach (var item in Documenti)
{
    Console.WriteLine(item.Note ?? "null");
    // Order returned: -4, 1, <empty string>, null
}

Remember, o => Int32.TryParse(...) is just a shorthand for creating a delegate that just takes in o as a parameter and returns Int32.TryParse(...). You can make it do whatever you want as long as it still is a syntacticly correct method with the correct signature (ex, all code paths return an int)

Cemafor
  • 1,633
  • 12
  • 27
2

That won't produce the expected results b/c TryParse returns a bool rather than int. The easiest thing to do is create a function that returns an int.

private int parseNote(string note) 
{   
  int num;   
  if (!Int32.TryParse(note, out num)) 
  {
    num = int.MaxValue; // or int.MinValue - however it should show up in sort   
  }

  return num; 
}

call that function from your sort

Documenti = Documenti
    .OrderBy(o => parseNote(o.Note))
    .ToList();

you could do it inline too, but, i think a separate method makes the code more readable. i'm sure the compiler will inline it, if it's an optimization.

Jason
  • 15,915
  • 3
  • 48
  • 72
  • I'd make `parseNote` return `int?` just because it makes sense to have something that isn't an int returned as "not an int". You should then still be able to make `null`s appear last, if required, by doing `.OrderByDescending(o => -parseNote(o.Note))`. –  May 17 '13 at 16:00
  • `That won't produce the expected results b/c TryParse returns a bool rather than int` It looks like he wants that; he wants to move all items that aren't ints to the end, not order them by their numeric value. – Servy May 17 '13 at 16:11
  • the code i submitted will sort objects by the int values and push items with non-int note values to the bottom, i thought that's what was asked for. the question is a bit vague on that front – Jason May 17 '13 at 16:20
1

C# 7 has some new features that make this even easier

var ints = from a in str.Split(',').Select(s=> new { valid = int.TryParse(s, out int i), result = i })
           where  a.valid
           select a.result;

or as you are asking specifically about sorting

var ints = from a in str.Split(',')
           orderby (int.TryParse(s, out int i) ? i : 0 )
           select a.result;
MikeT
  • 5,398
  • 3
  • 27
  • 43