ArgumentOutOfRangeException при сортировке DataGridView с использованием пользовательского IComparer

Настраивать

У меня есть фрагмент кода, который сортирует DataGridView с помощью пользовательского IComparer:

public class CustomComparer: IComparer
{
    public int Compare(object x, object y)
    {
        DataGridViewRow row1 = (DataGridViewRow)x;
        DataGridViewRow row2 = (DataGridViewRow)y;

        if (row1.ReadOnly && row2.ReadOnly)
        {
            return 0;
        }
        else if (row1.ReadOnly && !row2.ReadOnly)
        {
            return 1;
        }
        else
        {
            return -1;
        }
}

Проблема

Странно, когда я выполняю следующую строку (после заполнения строк):

grid.Sort(new CustomComparer());

Я получаю исключение ArgumentOutOfRangeException с сообщением «Индекс вышел за пределы допустимого диапазона. Параметр: индекс».

Больше фактов

Дальнейшее расследование выявило следующее:

  • DataGridView, который я сортирую, не имеет BindingSource — строки были добавлены вручную.
  • Трассировка стека ошибки имеет глубину всего один уровень — она возникает во внутреннем словаре в mscorlib.
  • Странный факт № 1. Это происходит только в том случае, если в какой-то момент мой пользовательский компаратор возвращает -1 для любого из своих сравнений.
  • Если я изменю метод Sort, чтобы больше не использовать мой CustomComparer, исключение не будет выдано.

Обходной путь

Этот последний факт заставил меня переписать метод Compare(), чтобы он соответствовал методу CompareTo .NET:

DataGridViewRow row1 = (DataGridViewRow)x;
DataGridViewRow row2 = (DataGridViewRow)y;

return row1.ReadOnly.CompareTo(row2.ReadOnly);

Что таинственным образом сработало. Исключение больше не выбрасывается.

Поэтому, хотя у меня есть обходной путь, мне интересно, есть ли у кого-нибудь идеи, почему это может быть исправлением, и в чем проблема могла заключаться в первую очередь. Я просмотрел реализацию CompareTo, и она также возвращает -1...


person Seb Charrot    schedule 08.01.2013    source источник
comment
Вы возвращаете -1, когда оба имеют ReadOnly из false.   -  person juharr    schedule 08.01.2013


Ответы (2)


juharr прав, но вот почему он прав:

Ваша реализация Compare не является симметричной, то есть если row1.ReadOnly == false и row2.ReadOnly == false вы возвращаете -1, то есть "row1 меньше row2". Если вы перевернете это сравнение с теми же значениями, то row2 станет меньше row1. Вероятно, это сбивает с толку алгоритм сортировки, который требует, чтобы Compare был симметричным.

Правильное сравнение должно быть:

    if (row1.ReadOnly == row2.ReadOnly)  // change && to ==
    {
        return 0;
    }
    else if (row1.ReadOnly && !row2.ReadOnly)
    {
        return 1;
    }
    else
    {
        return -1;
    }

что, вероятно, вернет bool.CompareTo(bool), поэтому ваш «обходной путь» (который, на мой взгляд, является лучшим решением) работает.

person D Stanley    schedule 08.01.2013
comment
Спасибо, я заметил асимметрию, но не подумал, что она может быть причиной исключения индекса вне диапазона. Во время моего расследования я переписал метод, чтобы он просто возвращал -1, чтобы убедиться, что проблема именно в этом. Ваше объяснение имеет смысл. - person Seb Charrot; 09.01.2013

Проблема в том, что вы возвращаете -1, когда оба имеют ReadOnly из false. В этом случае вы должны вернуть 0.

Просто измените свой первый оператор if на

 if((row1.ReadOnly && row2.ReadOnly) || !(row1.ReadOnly || row2.ReadOnly))
person juharr    schedule 08.01.2013