Объединение методов для int, long, double, BigInteger в один

187
06 марта 2018, 01:33

Здравствуйте, товарищи! Уже давно мучаюсь с тем, что не могу ограничить обобщенные методы определёнными классами. И вот вновь появилась такая потребность. Допустим, есть такой псевдометод:

public static T Method<T>(T Input)
{
    T answer;
    // Some code here...
    return answer;
}

Мне бы хотелось каким-то образом явно указать, что Т может быть лишь таким типом, который поддерживает арифметические операции, как то int, long, double, BigInteger etc.

Если так сделать невозможно, то расскажите, пожалуйста, подробнее, почему в С#-овском where была реализована возможность указывать лишь интерфейсы и некоторые другие вещи, но не конкретные запечатанные классы. Чтобы можно было написать нечто вроде:

public static T Method<T>(T Input) where T: (int or double or long or BigInteger)
{
    T answer;
    // Some code here...
    return answer;
}

Заранее спасибо за ответ!

Answer 1

Ответ на первую часть вопроса вам уже дали, вот ответ на вторую часть.

Генерики в C# могут иметь ограничения типа where T: C для конкретного класса C. Но вы не можете указывать ограничения типа «или» (хотя можно указывать ограничения типа «и»).

Почему сделано так? Дело в том, что компилятор не создаёт для каждого из типов генерик-параметра отдельный IL-код. Компилятор делает один и тот же IL-код для всех возможных значений T. Из этого простого факта следует много всяческих следствий.

Допустим, у нас есть вызов метода f() для переменной типа T, как это будет скомпилировано? Компилятор не знает, как вызвать у произвольного типа метод f(). Но если у вас есть ограничение where T : C, и у класса C есть метод f(), то компилятор и вызовет этот самый метод базового класса.

Допустим, мы хотим разрешить ограничение типа «или» — то есть, базовых типов может быть несколько. Если у нас, например, для одного из возможных типов T есть функция f(), а для другого нету, какой IL-код можно скомпилировать для исходного текста new T().f()? Даже если в обеих классах и есть метод f(), то к какому базовому типу приводить, чтобы вызвать метод? Компилятор не знает этого заранее.

По этой причине смысла разрешать ограничения типа «или» нет.

Ещё одна возможность была бы, если бы числовые типы имели общий класс-предок, и операции были бы виртуальными функциями у этого типа. Но это невозможно по многим причинам.

Во-первых, арифметические операции (сложение, вычитание, ...) определяются как статические функции. Во-вторых, структуры не могут участвовать в наследовании. Ну и в третьих, даже если бы для сложения чисел было сделано исключение, и операции выражались бы виртуальными функциями, тогда их невозможно было бы эффективно выполнять. Вспомните: сложение выполняется за одну инструкцию процессора, а вот для для вызова виртуальной функции необходимо обращение через таблицу виртуальных методов. Язык не может предоставлять вам настолько неэффективную реализацию!

Впрочем, если потеря скорости для вас некритична, вы можете реализовать операции самостоятельно, написав классы-обёртки для числовых типов, и определив соответствующие операции вручную. Тут вам придётся решать другое множество вопросов: например, как выразить тот факт, что сумма чисел типа int есть число типа int, а не double. (Возможно, вам пригодится интерфейс наподобие IAddable<T>.)

Разработчики C# понимают вашу проблему. Покамест в языке не нашлось эффективного решения, а давать плохое решение разработчики языка не хотят. Но работа в нужном направлении идёт: в одной из следующих версий языка должны появиться typeclass'ы (под названием shapes), с которыми можно будет эффективно выразить нужные ограничения на generic-тип (например, наличие оператора сложения).

Answer 2

Нет, такое сделать невозможно. Нужно для каждого типа писать свою перегрузку метода, как это сделано в linq.

Кстати, возможно, tt-файлы могут помочь с генерацией одинакового кода методов.

Answer 3

Ну, я думаю сказать это программе нельзя, однако можно попробовать вот так:

/// <summary>
/// Какой-то Метод. (Описание писать сюда)
/// </summary>
/// <typeparam name="T">Входной тип обязательно должен быть Числом!</typeparam>
/// <param name="Input">То, что нужно ввести</param>
public static T Method<T>(T Input)
{
    T answer;
    if (Input.GetType() != typeof(int) || Input.GetType() != typeof(double) || Input.GetType() != typeof(long) || Input.GetType() != typeof(Что-то ещё числовое))
    {
        throw new System.Exception("Атя-тя, нельзя сюда не число толкать!");
    }
    // Some code here...
    return answer;
}

Не очень уверен, что это удобно, но если программа зачем-то обратилась к вашему методу, кинув туда не число, то это явно косяк программы, а не метода, ведь так?

READ ALSO
Material Design TabControl

Material Design TabControl

Как сделать TabControl в стиле Materil DesignИспользую библиотеку MaterialDesignInXamltoolkit, но не понимаю, как на нем реализовать именно TabControl

192
Где должны хранится запросы к БД?

Где должны хранится запросы к БД?

Да, я знаю, что запросы к БД должны хранится в отдельном слое

191
Сортировка DataGridView

Сортировка DataGridView

Загружаю в DataGridView таблицу из ExcelХочу сделать сортировку по выбору из ComboBox

242
Особенности работы Async/Await

Особенности работы Async/Await

У меня есть следующая задача: необходимо в синхронной манере отправить запрос через шину данных и дождаться ответа, после чего вернуть управление...

196