List.Sort в C #: вызывается компаратор с нулевым объектом - PullRequest
19 голосов
/ 22 июня 2009

Я получаю странное поведение, используя встроенную функцию C # List.Sort с пользовательским компаратором.

По некоторым причинам иногда вызывается метод Compare класса сравнения с нулевым объектом в качестве одного из параметров. Но если я проверю список с помощью отладчика, в коллекции не будет нулевых объектов.

Мой класс для сравнения выглядит так:

public class DelegateToComparer<T> : IComparer<T>
{
    private readonly Func<T,T,int> _comparer;

    public int Compare(T x, T y)
    {
        return _comparer(x, y);
    }

    public DelegateToComparer(Func<T, T, int> comparer)
    {
        _comparer = comparer;
    }
}

Это позволяет передать делегат методу List.Sort, например:

mylist.Sort(new DelegateToComparer<MyClass>(
    (x, y) => { 
         return x.SomeProp.CompareTo(y.SomeProp); 
     });

Таким образом, вышеприведенный делегат сгенерирует исключение нулевой ссылки для параметра x , даже если никакие элементы mylist не являются нулевыми.

ОБНОВЛЕНИЕ: Да, я абсолютно уверен, что это параметр x , вызывающий исключение нулевой ссылки!

ОБНОВЛЕНИЕ: Вместо того, чтобы использовать метод List.Sort платформы, я попробовал пользовательский метод сортировки (например, new BubbleSort (). Sort (mylist) ), и проблема исчезла , Как я и подозревал, метод List.Sort по какой-то причине передает null в компаратор.

Ответы [ 7 ]

22 голосов
/ 23 марта 2010

Эта проблема возникает, когда функция сравнения не согласована, так что x

Вот пример, который воспроизводит проблему. Здесь это вызвано патологической функцией сравнения «compareStrings». Это зависит от начального состояния списка: если вы измените начальный порядок на «C», «B», «A», то исключения не будет.

Я бы не назвал это ошибкой в ​​функции сортировки - просто необходимо, чтобы функция сравнения была согласованной.

using System.Collections.Generic;

class Program
{
    static void Main()
    {
        var letters = new List<string>{"B","C","A"};

        letters.Sort(CompareStrings);
    }

    private static int CompareStrings(string l, string r)
    {
        if (l == "B")
            return -1;

        return l.CompareTo(r);
    }
}
3 голосов
/ 27 августа 2011

Я тоже сталкивался с этой проблемой (нулевая ссылка передавалась моей пользовательской реализации IComparer) и, наконец, обнаружил, что проблема была в использовании несовместимой функции сравнения.

Это была моя первоначальная реализация IComparer:

public class NumericStringComparer : IComparer<String>
{
    public int Compare(string x, string y)
    {
        float xNumber, yNumber;
        if (!float.TryParse(x, out xNumber))
        {
            return -1;
        }
        if (!float.TryParse(y, out yNumber))
        {
            return -1;
        }
        if (xNumber == yNumber)
        {
            return 0;
        }
        else
        {
            return (xNumber > yNumber) ? 1 : -1;
        }
    }
}

Ошибка в этом коде заключалась в том, что Compare возвращало -1, когда одно из значений не могло быть проанализировано должным образом (в моем случае это происходило из-за неправильно отформатированных строковых представлений числовых значений, поэтому TryParse всегда завершался неудачей).

Обратите внимание, что если x и y были отформатированы неправильно (и, следовательно, TryParse не удалось выполнить оба), вызовы Compare (x, y) и Compare (y, x) приведут к одному и тому же результату: -1. Это, я думаю, было главной проблемой. При отладке метод Compare () в какой-то момент передал бы пустой указатель строки в качестве одного из аргументов, даже если сортируемая коллекция не содержала пустой строки.

Как только я устранил проблему TryParse и обеспечил согласованность своей реализации, проблема ушла, и Compare больше не передавал нулевые указатели.

3 голосов
/ 22 июня 2009

Вы уверены, что проблема не в том, что SomeProp - это null?

В частности, со строками или Nullable<T> значениями.

Со строками было бы лучше использовать:

list.Sort((x, y) => string.Compare(x.SomeProp, y.SomeProp));

(редактировать)

Для нулевой безопасной оболочки вы можете использовать Comparer<T>.Default - например, для сортировки списка по свойству:

using System;
using System.Collections.Generic;
public static class ListExt {
    public static void Sort<TSource, TValue>(
            this List<TSource> list,
            Func<TSource, TValue> selector) {
        if (list == null) throw new ArgumentNullException("list");
        if (selector == null) throw new ArgumentNullException("selector");
        var comparer = Comparer<TValue>.Default;
        list.Sort((x,y) => comparer.Compare(selector(x), selector(y)));
    }
}
class SomeType {
    public override string ToString() { return SomeProp; }
    public string SomeProp { get; set; }
    static void Main() {
        var list = new List<SomeType> {
            new SomeType { SomeProp = "def"},
            new SomeType { SomeProp = null},
            new SomeType { SomeProp = "abc"},
            new SomeType { SomeProp = "ghi"},
        };
        list.Sort(x => x.SomeProp);
        list.ForEach(Console.WriteLine);
    }
}
0 голосов
/ 11 марта 2010

Я сам наткнулся на эту проблему и обнаружил, что она связана со свойством NaN в моих входных данных. Вот минимальный тестовый пример, который должен вызвать исключение:

public class C {

    double v;

    public static void Main() {
        var test =
            new List<C> { new C { v = 0d },
                          new C { v = Double.NaN },
                          new C { v = 1d } };
        test.Sort((d1, d2) => (int)(d1.v - d2.v));
    }

}
0 голосов
/ 23 июня 2009

Можете ли вы запустить этот код ...

mylst.Sort((i, j) =>
              {
                  Debug.Assert(i.SomeProp != null && j.SomeProp != null);
                  return i.SomeProp.CompareTo(j.SomeProp);
              }
         );
0 голосов
/ 23 июня 2009

В целях отладки вы хотите, чтобы ваш метод был нулевым. (или, по крайней мере, перехватите исключение null-ref. и обработайте его жестко закодированным способом). Затем используйте отладчик, чтобы посмотреть, какие другие значения сравниваются, в каком порядке и какие вызовы выполняются успешно или неудачно.

Тогда вы найдете свой ответ и сможете удалить нулевую безопасность.

0 голосов
/ 22 июня 2009

Марк полезен. Я согласен с ним, что NullReference происходит из-за вызова CompareTo для нулевого свойства. Не нуждаясь в классе расширения, вы можете сделать:

mylist.Sort((x, y) => 
      (Comparer<SomePropType>.Default.Compare(x.SomeProp, y.SomeProp)));

где SomePropType - это тип SomeProp

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...