17

I have a nested list,

List<List<String>> intable;

where I would like to sort all the columns. The problem is that the number of columns depends on user input.

Sorting the list like this works fine (assuming 4 columns for this example)

var tmp = intable.OrderBy(x => x[0]);
tmp = tmp.ThenBy(x => x[1]);
tmp = tmp.ThenBy(x => x[2]);
tmp = tmp.ThenBy(x => x[3]);
intable = tmp.ToList();

But, when I put it in a loop, like this:

var tmp = intable.OrderBy(x => x[0]);
for (int i = 1; i <= 3; i++)
{
        tmp = tmp.ThenBy(x => x[i]);
}
intable = tmp.ToList();

it no longer works correctly, and sorts only the fourth column.

Yuck
  • 49,664
  • 13
  • 105
  • 135
phil
  • 390
  • 3
  • 12
  • 1
    See this [http://social.msdn.microsoft.com/forums/en-US/linqprojectgeneral/thread/61e502b4-6795-4e51-b70e-2be642cfc413/](http://social.msdn.microsoft.com/forums/en-US/linqprojectgeneral/thread/61e502b4-6795-4e51-b70e-2be642cfc413/) – Raj Ranjhan Jan 27 '12 at 18:22

2 Answers2

28

This is a case of access to a modified closure. Change the code to this and it will work:

var tmp = intable.OrderBy(x => x[0]);
for (int i = 1; i <= 3; i++) {
    var thisI = i;
    tmp = tmp.ThenBy(x => x[thisI]);
}
intable = tmp.ToList();

Eric Lippert has written a two-part article describing the problem. The reason it doesn't work as you expect to is - in short - because LINQ is only using the last value of i when it is evaluated when you call ToList(). It's the same as if you had written:

var tmp = intable.OrderBy(x => x[0]);
tmp = tmp.ThenBy(x => x[3]);
tmp = tmp.ThenBy(x => x[3]);
tmp = tmp.ThenBy(x => x[3]);
intable = tmp.ToList();
Yuck
  • 49,664
  • 13
  • 105
  • 135
  • 5
    Eric Lippert did confirm that the closure behavior will change in C# 5. [SO Link Here](http://stackoverflow.com/a/8899347/498969) – Adam Spicer Jan 27 '12 at 18:26
  • 7
    @AdamSpicer: It will not change for "for" loops, just for "foreach" loops. – Eric Lippert Jan 29 '12 at 16:58
  • 1
    I was trying to do just this and this answer was tremendously helpful. In my case I had several variables that needed to be re-declared (or whatever you call it) inside the loop to be correctly accessible in the closure. – Dave Lowther May 31 '13 at 19:11
0

Create a comparer

class StringListComparer : IComparer<List<string>>
{
    public int Compare(List<string> x, List<string> y)
    {
        int minLength = Math.Min(x.Count, y.Count);
        for (int i = 0; i < minLength; i++) {
            int comp = x[i].CompareTo(y[i]);
            if (comp != 0) {
                return comp;
            }
        }
        return x.Count.CompareTo(y.Count);
    }
}

then sort the list like this

intable.Sort(new StringListComparer());
Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188