Сортировка IComparable объектов, некоторые из которых являются нулевыми - PullRequest
6 голосов
/ 31 мая 2011

Большинство людей, когда пишут тип (класс) refence, который реализует IComparable , используют соглашение о том, что null меньше, чем любой реальный объект. Но если вы попытаетесь использовать противоположное соглашение, произойдет что-то интересное:

using System;
using System.Collections.Generic;

namespace SortingNulls
{
  internal class Child : IComparable<Child>
  {
    public int Age;
    public string Name;

    public int CompareTo(Child other)
    {
      if (other == null)
        return -1; // what's your problem?

      return this.Age.CompareTo(other.Age);
    }

    public override string ToString()
    {
      return string.Format("{0} ({1} years)", this.Name, this.Age);
    }
  }

  internal static class Program
  {
    private static void Main()
    {
      var listOfChilds = new List<Child>
      {
        null,
        null,
        null,
        null,
        new Child { Age = 5, Name = "Joe" },
        new Child { Age = 6, Name = "Sam" },
        new Child { Age = 3, Name = "Jude" },
        new Child { Age = 7, Name = "Mary" },
        null,
        null,
        null,
        null,
        new Child { Age = 7, Name = "Pete" },
        null,
        new Child { Age = 3, Name = "Bob" },
        new Child { Age = 4, Name = "Tim" },
        null,
        null,
      };

      listOfChilds.Sort();

      Console.WriteLine("Sorted list begins here");
      for (int i = 0; i < listOfChilds.Count; ++i)
        Console.WriteLine("{0,2}: {1}", i, listOfChilds[i]);
      Console.WriteLine("Sorted list ends here");
    }
  }
}

При запуске приведенного выше кода вы видите, что нулевые ссылки отсортированы не так, как ожидалось. Очевидно, что при сравнении A с B, если A является объектом, а B является нулем, используется пользовательское сравнение, но если, наоборот, A является нулем, а B является объектом, вместо этого используется некоторое сравнение BCL.

Это ошибка?

Ответы [ 4 ]

8 голосов
/ 31 мая 2011

Нет, это не ошибка. Ваш CompareTo метод, который реализует IComparable<Child>, определен в вашем Child классе. Другими словами, если вам нужно вызвать метод для одного из ваших типов, чтобы сделать сравнение.

Если один из сравниваемых Child элементов является нулевым, как вы можете вызвать CompareTo для него?

Обратите внимание, что из определения IComparable :

"По определению, любой объект сравнивает больше (или следует) с нулевой ссылкой (Nothing в Visual Basic), а две нулевые ссылки сравниваются равными друг другу."

Что объясняет результаты, которые вы наблюдаете.

Решение состоит в том, чтобы делегировать другому классу для сравнения. См. Интерфейс IComparer .

1 голос
/ 31 мая 2011

Нет, ваш код содержит ошибку, поскольку он не соответствует стандартам, которые определяют IComparable.CompareTo(): IComparable

В частности: По определению, любой объект сравнивает больше (или следует) null, а две null ссылки сравниваются равными друг другу.

В вашем примере вы определяете свой объект для сравнения меньше (или предшествует) null, что в точности противоположно тому, как это должно быть сделано.

1 голос
/ 31 мая 2011

Значение по умолчанию Comparer<T> для типа T должно учитывать сценарий, в котором первый элемент (назовем его A) равен null. Допустим, это выглядит примерно так:

if (ReferenceEquals(a, null))
{
    return -1;
}

return a.CompareTo(b);

Это основано на документации List<T>.Sort:

Этот метод использует компаратор по умолчанию Comparer(Of T).Default для типа Т до определить порядок элементов списка.

Возможно, верхний шаг может вернуть 0, только если оба элемента равны null, а в противном случае использовать b.CompareTo(a).

.

Я бы не стал называть это ошибкой . Это просто то, что нужно знать.

1 голос
/ 31 мая 2011

Что может произойти, если вы попытаетесь оценить this.Age.CompareTo(other.Age);, если this равно null?На самом деле, this никогда не может быть null в C #.

Что касается вопроса, является ли это ошибкой, см. это сообщение в блоге .

...