Список <T>. Сортировка нуля сортировки неправильно - PullRequest
3 голосов
/ 09 марта 2010

У меня есть список объектов, некоторые из которых могут быть нулевыми. Я хочу, чтобы это сортировалось по какому-либо свойству, с null s в конце списка. Тем не менее, метод List<T>.Sort(), похоже, ставит null s в начале независимо от того, что возвращает компаратор. Это небольшая программа, которую я использовал для проверки этого:

class Program
 {
  class Foo {
   public int Bar;
   public Foo(int bar)
   {
    Bar = bar;
   }
  }
  static void Main(string[] args)
  {
   List<Foo> list = new List<Foo>{null, new Foo(1), new Foo(3), null, new Foo(100)};

   foreach (var foo in list)
   {
     Console.WriteLine("Foo: {0}", foo==null?"NULL":foo.Bar.ToString());
   }

   Console.WriteLine("Sorting:");
   list.Sort(new Comparer());
   foreach (var foo in list)
   {
    Console.WriteLine("Foo: {0}", foo == null ? "NULL" : foo.Bar.ToString());
   }
   Console.ReadKey();
  }
  class Comparer:IComparer<Foo>
  {
   #region Implementation of IComparer<in Foo>

   public int Compare(Foo x, Foo y)
   {
    int xbar = x == null ? int.MinValue : x.Bar;
    int ybar = y == null ? int.MinValue : y.Bar;
    return ybar - xbar;
   }

   #endregion
  }
 }

Попробуйте сами: отсортированный список печатается как

Foo: NULL
Foo: NULL
Foo: 100
Foo: 3
Foo: 1

null s Сначала, даже если их сравнивают как int.Minvalue. Метод глючит или как?

Ответы [ 5 ]

13 голосов
/ 09 марта 2010

Ваша реализация неверна, потому что вам не удалось сначала написать спецификацию, а затем написать код, соответствующий спецификации.

Итак, начните сначала. Написать спецификацию:

  • FooCompare принимает две ссылки на Foo, x и y. Любой из них может быть нулевым.
  • IComparer.Compare (x, y) задокументировано как возвращающий ноль, если x и y равны, отрицательный, если y больше x, и положительный, если y меньше x. Мы будем следовать этому соглашению.
  • если x и y равны нулю, они равны.
  • если один равен нулю, а другой - нет, то нулевой будет меньше.
  • если ни один из них не равен нулю, то сравнение основано на значении свойства Bar.

И теперь вы можете написать код, который вам гарантирован соответствует спецификации:

// FooCompare takes two references to Foo, x and y. Either may be null.
// +1 means x is larger, -1 means y is larger, 0 means they are the same.

int FooCompare(Foo x, Foo y)
{
    // if x and y are both null, they are equal.
    if (x == null && y == null) return 0;
    // if one is null and the other one is not then the non-null one is larger.
    if (x != null && y == null) return 1; 
    if (y != null && x == null) return -1; 
    // if neither are null then the comparison is 
    // based on the value of the Bar property.
    var xbar = x.Bar; // only calculate x.Bar once
    var ybar = y.Bar; // only calculate y.Bar once
    if (xbar > ybar) return 1;
    if (ybar > xbar) return -1;
    return 0;
}

// The original poster evidently wishes the list to be sorted from
// largest to smallest, where null is the smallest.
// Reverse the polarity of the neutron flow:
int Compare(Foo x, Foo y)
{
    return FooCompare(y, x);
}

Не связывайтесь с хитрыми арифметическими хитростями; как вы видели, умный равен багги. Сначала правильность.

12 голосов
/ 09 марта 2010

Допустим, x равно null, а y равно 1.

int xbar = x == null ? int.MinValue : x.Bar;
int ybar = y == null ? int.MinValue : y.Bar;
return ybar - xbar;

Так что теперь у нас есть 1 - int.MinValue. Это будет переполнение, поэтому конечный результат будет отрицательным , следовательно, null будет считаться меньше 1.

По этой причине вычисления в корне ошибочны.

public int Compare(Foo x, Foo y)
{
    int xbar = x == null ? int.MinValue : x.Bar;
    int ybar = y == null ? int.MinValue : y.Bar;
    return ybar.CompareTo(xbar);
}

(Спасибо комментаторам за указание на недостаток в моем методе.)

4 голосов
/ 09 марта 2010

Заменить эту строку:

return ybar - xbar;

с этой строкой:

return ybar.CompareTo(xbar);

Потому что ваша арифметика переполнена.

2 голосов
/ 09 марта 2010

Ваша реализация IComparer.Compare излишне сложна. Помните, что все, что имеет значение для возвращаемого значения, является знаком - величина не важна. Чтобы null s сравнивать меньше, чем все действительные Foo s, просто перейдите к реализации Int32 s CompareTo, после выполнения замены:

return       (x == null ? int.MinValue : x.Bar)
  .CompareTo((y == null ? int.MinValue : y.Bar))
0 голосов
/ 09 марта 2010

int.MinValue равно -2147483648.

Теперь предположим, что вы сравнивали 3 и -2147483648 с помощью вычитания, в частности 3 - (-2147483648?

Также вам следует рассмотреть возможность использования оператора объединения нулей ??

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