Как проверить, является ли тип (общий тип) интегральным или неинтегральным типом в С#?

У меня есть общий тип T. Использование класс Marc Operator Я могу выполнять вычисления на нем.

Можно ли путем простых вычислений определить, является ли тип интегралом или nonintegral?

Возможно, есть лучшее решение? Я бы предпочел поддерживать любой возможный тип, поэтому я хотел бы предотвратить жесткое кодирование, типы которого являются интегральными/неинтегральными.

Фоновая информация

Ситуация, в которой я нахожусь, заключается в том, что я хочу передать double в T, но округлить до ближайшего значения T до значения double.

int a = (int)2.6 приводит к 2, в то время как я хочу, чтобы он привел его в 3, не зная тип (в данном случае int). Это также может быть double, и в этом случае я хочу, чтобы результат был 2.6.

Ответ 1

Вы пробовали Convert.ChangeType? Что-то вроде:

Convert.ChangeType(1.9d, typeof (T))

Он будет работать для всех числовых типов, которые, как я думаю (до тех пор, пока первый параметр iConvertible и тип поддерживается, который, как я полагаю, должен иметь все основные численные значения).

Важно отметить, что это вызовет нечто вроде double.ToInt32, который округляет значения, а не обрезает (округление банкиров, я считаю).

Я тестировал это в небольшой программе LinqPad, и он делает то, что я думаю, что вы хотите:

void Main()
{
    var foo = RetNum<decimal>();
    foo.Dump();
}

public static T RetNum<T>()
{
    return (T)Convert.ChangeType(1.9d, typeof (T));
}

Ответ 2

Здесь метод, который будет определять, является ли конкретное значение, хранящееся в общем числовом типе, целым числом без жесткого кодирования. Протестировано для меня на .NET 4. Правильно обрабатывает все встроенные числовые типы (как определено в ссылке MSDN внизу), кроме BigInteger, которая не реализует IConvertible.

        public static bool? IsInteger<T>(T testNumber) where T : IConvertible
        {
            // returns null if T is non-numeric
            bool? isInt = null;
            try
            {
                isInt = testNumber.ToUInt64(CultureInfo.InvariantCulture) == testNumber.ToDouble(CultureInfo.InvariantCulture);
            }
            catch (OverflowException)
            {
                // casting a negative int will cause this exception
                try
                {
                    isInt = testNumber.ToInt64(CultureInfo.InvariantCulture) == testNumber.ToDouble(CultureInfo.InvariantCulture);
                }
                catch
                {
                    // throw depending on desired behavior
                }
            }
            catch
            {
                // throw depending on desired behavior
            }
            return isInt;
        }

Здесь метод, который определит, является ли конкретный тип интегральным типом.

    public static bool? IsIntegerType<T>() where T : IConvertible
    {
        bool? isInt = null;
        try
        {
            isInt = Math.Round((double)Convert.ChangeType((T)Convert.ChangeType(0.1d, typeof(T)),typeof(double)), 1) != .1d;
            // if you don't round it and T is float you'll get the wrong result
        }
        catch
        {   
            // T is a non numeric type, or something went wrong with the activator
        }
        return isInt;
    }

Convert.ChangeType - способ конвертировать с округлением между двумя типичными числовыми типами. Но для пинков и любопытства здесь можно преобразовать общий числовой тип в int, который может быть расширен, чтобы возвращать общий тип без особых трудностей.

    public static int GetInt32<T>(T target) where T : IConvertible
    {
        bool? isInt = IsInteger<T>(target);
        if (isInt == null) throw new ArgumentException(); // put an appropriate message in
        else if (isInt == true)
        {
            try
            {
                int i = target.ToInt32(CultureInfo.InvariantCulture);
                return i;
            }
            catch
            {   // exceeded size of int32
                throw new OverflowException(); // put an appropriate message in
            }
        }
        else
        {
            try
            {
                double d = target.ToDouble(CultureInfo.InvariantCulture);
                return (int)Math.Round(d);
            }
            catch
            {   // exceeded size of int32
                throw new OverflowException(); // put an appropriate message in
            }
        }
    }

Мои результаты:

        double d = 1.9;
        byte b = 1;
        sbyte sb = 1;
        float f = 2.0f;
        short s = 1;
        int i = -3;
        UInt16 ui = 44;
        ulong ul = ulong.MaxValue;
        bool? dd = IsInteger<double>(d); // false
        bool? dt = IsInteger<DateTime>(DateTime.Now); // null
        bool? db = IsInteger<byte>(b); // true
        bool? dsb = IsInteger<sbyte>(sb); // true
        bool? df = IsInteger<float>(f); // true
        bool? ds = IsInteger<short>(s); // true
        bool? di = IsInteger<int>(i); // true
        bool? dui = IsInteger<UInt16>(ui); // true
        bool? dul = IsInteger<ulong>(ul); // true
        int converted = GetInt32<double>(d); // coverted==2
        bool? isd = IsIntegerType<double>(); // false
        bool? isi = IsIntegerType<int>(); // true

Кроме того, эта страница MSDN содержит примерный код, который может быть полезен. В частности, он включает список типов, которые считаются числовыми.

Ответ 3

Я не уверен на 100%, что вы просите, но:

Чтобы проверить, является ли он целым типом, используйте это: if (obj is float || obj is double) или if typeof(T) == typeof(float) || typeof(T) == typeof(double))

Чтобы проверить, является ли это интегральным значением, переведите его в double и затем if(value == Math.Round(value))

Конечно, предполагается, что у вас есть номер в первую очередь. Я считаю, что класс Operator, который вы используете, поддерживает такие вещи, как DateTime. Было бы лучше, если бы ваш общий метод имел общее ограничение where T : IConvertible? Таким образом, были бы явные методы ToDouble и ToInteger.

Edit

Думаю, я понимаю: у вас есть две локальные переменные, double d; T num;. Вы хотите использовать d для ввода T, но с правильным округлением, если T является интегральным типом. Это правильно?

Предполагая, что правильно, вот что я сделал бы:

public void SomeMethod<T>()
{
    double d;
    // I think I got all the floating-point types. There only a few, so we can test for them explicitly.
    if(typeof(T) != typeof(double) && typeof(T) != typeof(float) && typeof(T) != typeof(Decimal))
    {
        d = Math.Round(d);
    }
    T converted = Convert.ChangeType(d, typeof(T));
}

Ответ 4

Ответ Криса дает возможное решение упомянутого сценария, но по соображениям производительности я все еще пытаюсь ответить на фактический вопрос.

Предполагаемое (непроверенное) значение Convert.ChangeType намного медленнее, чем Math.Round(). В идеале я могу проверить один раз, является ли данный тип интегральным или нет, и условно вызывать Math.Round() с этого момента, чтобы получить гораздо более эффективное решение, чем постоянное вызов Convert.ChangeType().

Я пытаюсь выполнить следующую реализацию:

  • Преобразуйте как 3, 2, так и 1 в нужный неизвестный тип. (Это предполагает, что возможно преобразование из int в числовой тип, что всегда должно быть возможно.)
  • В случае 3 / 2 == 1 он является интегральным типом. В противном случае это нецелый тип.

Это решение нигде не опирается на знание типа и использует только конверсии и вычисления.