OrderBy/ThenBy looping - вложенные списки в С#

У меня есть вложенный список,

List<List<String>> intable;

где я хотел бы отсортировать все столбцы. Проблема в том, что количество столбцов зависит от пользовательского ввода.

Сортировка списка, как это работает отлично (при условии, что для этого примера четыре столбца)

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();

Но, когда я помещаю его в цикл, вот так:

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

он работает некорректно и сортирует только четвертый столбец.

Ответы

Ответ 1

Это случай доступа к модифицированному закрытию. Измените код на это, и он будет работать:

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();

Эрик Липперт написал статью из двух частей, описывающую проблему. Причина, по которой он не работает, как вы ожидаете, - короче - потому что LINQ использует только последнее значение i, когда оно оценивается при вызове ToList(). Это так же, как если бы вы написали:

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();

Ответ 2

Создать компаратор

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);
    }
}

затем отсортируйте список следующим образом

intable.Sort(new StringListComparer());