Могу ли я использовать оператор объединения целых чисел для цепочки compareTo?

#c# #coalesce

#c# #объединение

Вопрос:

Я хочу сделать что-то вроде этого:

 public override int CompareTo (Foo rhs)
{
    return Bar.CompareTo(rhs.Bar) ??
           Baz.CompareTo(rhs.Baz) ??
           Fuz.CompareTo(rhs.Fuz) ?? 0;
}
  

Это работает не так, как написано; есть ли какой-нибудь минимальный обходной путь, чтобы заставить его работать? По сути, я хочу, чтобы 0 цеплялось до ненулевого значения (или конца цепочки).

Комментарии:

1. Я не знаю ответа на ваш вопрос, но если для определения возвращаемого значения можно использовать три значения, вы можете рассмотреть возможность перепроектирования вашего класса.

2. @DJ CompareTo связан с сортировкой; на самом деле это довольно распространенное явление — например, сортировка по фамилии — если это то же самое, затем сортировка по имени, затем, если все равно равно, сортировка по некоторому уникальному идентификатору (например, идентификатор базы данных).

3. CompareTo Возвращают ли методы null? ?? работает только с null.

4. @Marc Интересно, я раньше с этим не сталкивался. Я вижу, что compareTo является частью класса IComparable. Мне нужно будет прочитать больше, чтобы получить представление. Спасибо

Ответ №1:

Не поддерживается языком. Но вы можете написать небольшой помощник, подобный этому:

 public override int CompareTo (Foo rhs)
{
    return FirstNonZeroValue(
        () => Bar.CompareTo(rhs.Bar), 
        () => Baz.CompareTo(rhs.Baz),
        () => Fuz.CompareTo(rhs.Fuz));
}

private int FirstNonZeroValue(params Func<int>[] comparisons)
{
    return comparisons.Select(x => x()).FirstOrDefault(x => x != 0);
}
  

Комментарии:

1. Тьфу. Подобные решения иллюстрируют, насколько отстойно, что у нас не может быть глобальных функций.

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

3. Расширение для чего? Int32? Это вообще возможно?

4. Вы можете сделать это на int, хотя я бы не рекомендовал это. Вы могли бы сделать это IComparable , чтобы разрешить код, подобный this.FirstNonZeroValue( ... ) или on IEnumerable<Func<int>> (но тогда вы теряете удобство использования списка params параметров.

5. Ах, я не думал делать это в интерфейсе. Это здорово!

Ответ №2:

В принципе, нет, но было бы неплохо, если бы это было так (IIRC, Джон подробно упомянул аналогичную идею в C #). Вероятно, вы могли бы связать условные выражения, но я склонен просто использовать:

 int delta = Bar.CompareTo(rhs.Bar);
if(delta == 0) delta = Baz.CompareTo(rhs.Baz);
if(delta == 0) delta = Fuz.CompareTo(rhs.Fuz);
return delta;
  

Ответ №3:

Не совсем, ?? работает только для нулевых значений (ссылочных типов или обнуляемых структур)

 int i;

i = Bar.CompareTo(rhs.Bar);
if (i != 0) return i;

i = Baz.CompareTo(rhs.Baz);
if (i != 0) return i;

i = Fuz.CompareTo(rhs.Fuz);
if (i != 0) return i;

return 0;
  

Ответ №4:

Если короткое замыкание не требуется, вы можете выполнить некоторую двоичную арифметику. Не требуется, чтобы результаты сравнения были -1, 0 или 1, но они должны быть < 0, = 0, > 0 . Таким образом, используя 2 ^ n в качестве коэффициента, где n — приоритет, вы можете суммировать результаты сравнения и получить число> 0 или<0 по мере необходимости.

Итак, используйте

 return 4 * Bar.CompareTo(rhs.Bar)
       2 * Baz.CompareTo(rhs.Baz)
       1 * Fuz.CompareTo(rhs.Fuz);
  

Ответ №5:

Вы также можете изменить ноль на null с помощью функции и получить отложенную оценку без Func<> :

 public override int CompareTo (Foo rhs) =>
    Bar.CompareTo(rhs.Bar).GetNullIfZero() ??
    Baz.CompareTo(rhs.Baz).GetNullIfZero() ??
    Fuz.CompareTo(rhs.Fuz);

public static class IntExtensions
{
    public static int? GetNullIfZero(this int i) => i == 0 ? null : (int?)i;
}
  

Существует аналогичный способ для ленивых операций со строковыми цепочками GetNullIfEmpty GetNullIfWhiteSpace с расширениями a и .