С#: общие математические функции (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
и т.д.