С#: общие математические функции (Min, Max и т.д.)

Я думал о написании общих функций для основных математических операций, таких как Min, Max и т.д. Но я не знаю, как сравнить два типичных типа:

public T Max<T>(T v1, T v2) where T: struct
{
   return (v1 > v2 ? v1 : v2);
}

Как насчет этого?

Спасибо.

Ответы

Ответ 1

Если вы хотите создавать функции сравнения, вы можете использовать сопоставление по умолчанию для типа T. Например:

public static T Max<T>(T x, T y)
{
    return (Comparer<T>.Default.Compare(x, y) > 0) ? x : y;
}

Если T реализует IComparable<T>, тогда будет использован этот компаратор; если T не реализует IComparable<T>, а реализует IComparable, тогда будет использоваться этот компаратор; если T не реализует либо IComparable<T>, либо IComparable, тогда будет выбрано исключение времени выполнения.

Если вам нужно/нужно делать больше, чем просто сравнивать элементы, вы могли бы посмотреть на реализацию общих операторов в MiscUtil и связанная статья.

Ответ 2

Вы, вероятно, захотите ограничить общие типы для реализации IComparable:

public T Max<T>(T v1, T v2) where T: struct, IComparable<T>

а затем используйте метод CompareTo:

{
    return (v1.CompareTo(v2) > 0 ? v1 : v2);
}

Ответ 3

Позвольте мне не согласиться. Реализация @LukeH не является общей.

Я объясню, почему он не является общим:

Comparer<T>.Default включает проверку T во время выполнения, чтобы определить, реализует ли он IComparable<T>, IComparable или ни один из них. Хотя это поведение плохо описано в http://msdn.microsoft.com/en-us/library/azhsac5f.aspx, мы можем вычесть его, потому что Comparer<T>.Default генерирует исключение, когда T не реализует ни один из них. Если проверка была проведена во время компиляции, не было бы необходимости в исключении (время выполнения), при этом ошибка времени компиляции была бы достаточной.

Затем, поскольку Comparer<T>.Default использует Reflection, это означает высокую стоимость во время выполнения, тогда...., Это не общий... Почему?

Поскольку Generic Programming означает: Один алгоритм (Generic) может охватывать многие реализации (для многих типов), поддерживающие эффективность рукописных версий.

Возьмем пример. Рукописная версия для целых чисел:

public static int Max( int x, int y)
{
    return (x.CompareTo(y) > 0) ? x : y;
}

Это очень просто, включая только сравнение (или, может быть, больше, в зависимости от того, как реализуется Int32.CompareTo()). Если мы используем реализацию @LukeH, мы добавляем Reflection к чему-то очень простому.

Короче:

  • Всегда предпочитайте ошибки времени компиляции для исключений времени выполнения (это не Javascript, Ruby,...:-))
  • Эта реализация менее эффективна по сравнению с рукописной версией (для любого типа)

С другой стороны. Как вы думаете, Макс должен вернуться, когда x и y являются эквивалентами?

Я начинаю анализировать реалистичные реализации....

Идеальная реализация будет чем-то вроде...

    public static T Max<T>(T x, T y, Func<T, T, int> cmp)
    {
        return (cmp(x, y) > 0) ? x : y;
    }

    //Pseudo-code ( note the 'or' next to 'where' )
    public static T Max<T>(T x, T y) where T: IComparable<T> or IComparable
    {
        return Max(x, y, (a, b) => { return a.CompareTo(b); });
    }

Это невозможно в С#, следующая попытка может быть...

    //pseudo-code
    public static T Max<T>(T x, T y, Func<T, T, int> cmp)
    {
        return (cmp(x, y) > 0) ? x : y;
    }

    public static T Max<T>(T x, T y) where T: IComparable<T>
    {
        return Max(x, y, (a, b) => { return a.CompareTo(b); });
    }

    public static T Max<T>(T x, T y) where T: IComparable
    {
        return Max(x, y, (a, b) => { return a.CompareTo(b); });
    }

Но это не возможно, потому что разрешение перегрузки не учитывает ограничения Generics....

Тогда я не буду сознавать IComparable. Я просто буду беспокоиться о IComparable<T>

    public static T Max<T>(T x, T y, Func<T, T, int> cmp)
    {
        return (cmp(x, y) > 0) ? x : y;
    }

    public static T Max<T>(T x, T y) where T: IComparable<T>
    {
        return Max(x, y, (a, b) => { return a.CompareTo(b); });
    }

Ответ 4

Это слишком поздно, но почему бы не использовать динамические типы и делегаты в качестве альтернативы IComparable? Таким образом, вы получаете безопасность типа компиляции в большинстве случаев и будете получать только ошибку времени выполнения, когда поставляемые типы не поддерживают оператор < и вы не можете предоставить сопоставление по умолчанию в качестве аргумента.

public static T Max<T>(T first, T second, Func<T,T,bool> f = null)
{
    Func<dynamic,dynamic,bool> is_left_smaller = (x, y) => x < y ? true : false;

    var compare = f ?? new Func<T, T, bool>((x, y) => is_left_smaller(x, y));

    return compare(first, second) ? second : first; 
}

Ответ 5

Из памяти T также должно быть IComparable (добавить это к where), а затем вы используете v1.CompareTo(v2) > 0 и т.д.