C # - як визначити, чи є тип числом


105

Чи є спосіб визначити, чи вказаний тип .Net - це число? Наприклад: System.UInt32/UInt16/Doubleчи всі числа. Я хочу уникати тривалого вимикача на корпусі Type.FullName.


4
Дуп багатьох, багатьох, багатьох. Чому це ще не закрито?
Нолдорін

2
Дублікат stackoverflow.com/questions/1130698 і дуже близький до деяких інших.
Хенк Холтерман

Відповіді:


110

Спробуйте це:

Type type = object.GetType();
bool isNumber = (type.IsPrimitiveImple && type != typeof(bool) && type != typeof(char));

Примітивними типами є булевий, байт, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, Char, Double та Single.

Беручи рішення Гійома трохи далі:

public static bool IsNumericType(this object o)
{   
  switch (Type.GetTypeCode(o.GetType()))
  {
    case TypeCode.Byte:
    case TypeCode.SByte:
    case TypeCode.UInt16:
    case TypeCode.UInt32:
    case TypeCode.UInt64:
    case TypeCode.Int16:
    case TypeCode.Int32:
    case TypeCode.Int64:
    case TypeCode.Decimal:
    case TypeCode.Double:
    case TypeCode.Single:
      return true;
    default:
      return false;
  }
}

Використання:

int i = 32;
i.IsNumericType(); // True

string s = "Hello World";
s.IsNumericType(); // False

2
Отже, decimalтип не є числовим?
ЛукаH

2
@Xaero: Я не сумніваюся, що decimal це число. Тільки тому, що це не примітив, не означає, що він не числовий. Ваш код повинен враховувати це.
ЛукаH

2
Це потрібно буде переробити для нових числових типів у .NET 4.0, які не мають кодів типів.
Джон Скіт

7
Як ти можеш відповісти мені на відповідь на основі сучасних технологій. Можливо, в .NET 62, int буде видалено - ви збираєтесь спростувати всі відповіді з int?
Філіп Уоллес

1
@DiskJunky Вибач, друже. Це було майже три роки тому, і я не пам’ятаю, який був їхній зміст.
kdbanman

93

Не використовуйте перемикач - просто використовуйте набір:

HashSet<Type> NumericTypes = new HashSet<Type>
{
    typeof(decimal), typeof(byte), typeof(sbyte),
    typeof(short), typeof(ushort), ...
};

EDIT: Однією з переваг цього над використанням типу типу є те, що при введенні нових числових типів у .NET (наприклад, BigInteger та Complex ) легко налаштувати - тоді як ці типи не отримають код типу.


4
і як би ви використовували HashSet?
RvdK

8
Числові типи. Містить (що завгодно)?
mqp

3
bool isANumber = NumericTypes.Contains (classInstance.GetType ());
Юрій Факторович

Можна було б подумати, що компілятор зробить неявну конверсію переключення-оператора в хешсет.
Rolf Kristensen

6
@RolfKristensen: Ну switchпросто не працює Type, тому не можна. Можна, TypeCodeзвичайно, увімкнути , але це вже інша справа.
Джон Скіт

69

Жодне з рішень не враховує Nullable.

Я трохи змінив рішення Джона Скіта:

    private static HashSet<Type> NumericTypes = new HashSet<Type>
    {
        typeof(int),
        typeof(uint),
        typeof(double),
        typeof(decimal),
        ...
    };

    internal static bool IsNumericType(Type type)
    {
        return NumericTypes.Contains(type) ||
               NumericTypes.Contains(Nullable.GetUnderlyingType(type));
    }

Я знаю, що міг би просто додати нульові позначки до свого HashSet. Але це рішення дозволяє уникнути небезпеки забути додати певний Nullable до свого списку.

    private static HashSet<Type> NumericTypes = new HashSet<Type>
    {
        typeof(int),
        typeof(int?),
        ...
    };

2
Чи справді числовий тип є нульовим? Наскільки мені відомо, нуль - це не число.
IllidanS4 хоче, щоб Моніка повернулася

2
Це залежить від того, чого ви хочете досягти. У моєму випадку мені також потрібно було включати нуклеуси. Але я також міг думати про ситуації, коли це не бажана поведінка.
Юрген Штейнблок

Добре! Розглядати нульове число як число дуже корисно для перевірки вхідного інтерфейсу.
guogangj

1
@ IllidanS4 Чек встановлено на тип " Не значення". У більшості випадків мінливі числові типи слід розглядати як числові. Звичайно, якщо перевірка була на значення, а значення - нульове, то так, це не повинно вважатися числовим.
nawfal

40
public static bool IsNumericType(Type type)
{
  switch (Type.GetTypeCode(type))
  {
    case TypeCode.Byte:
    case TypeCode.SByte:
    case TypeCode.UInt16:
    case TypeCode.UInt32:
    case TypeCode.UInt64:
    case TypeCode.Int16:
    case TypeCode.Int32:
    case TypeCode.Int64:
    case TypeCode.Decimal:
    case TypeCode.Double:
    case TypeCode.Single:
      return true;
    default:
      return false;
  }
}

Зверніть увагу на видалення оптимізації (див. Коментарі до enzi). Якщо ви дійсно хочете її оптимізувати (втрачаючи читабельність та певну безпеку ...):

public static bool IsNumericType(Type type)
{
  TypeCode typeCode = Type.GetTypeCode(type);
  //The TypeCode of numerical types are between SByte (5) and Decimal (15).
  return (int)typeCode >= 5 && (int)typeCode <= 15;
}


13
Я знаю, що ця відповідь стара, але нещодавно я натрапив на такий перемикач: не використовуйте запропоновану оптимізацію! Я переглянув ІЛ-код, сформований таким перемикачем, і зазначив, що компілятор вже застосовує оптимізацію (в IL 5 віднімається з коду типу, а потім значення від 0 до 10 вважаються істинними). Отже, перемикач слід використовувати, щоб він був більш читабельним і безпечнішим і таким же швидким.
enzi

1
Якщо ви насправді хочете оптимізувати його і не піклуватися про читабельність, то оптимальним кодом було б return unchecked((uint)Type.GetTypeCode(type) - 5u) <= 10u;видалити гілку, яку ввів &&.
AnorZaken

14

В основному рішення Skeet, але ви можете повторно використовувати його з типами Nullable наступним чином:

public static class TypeHelper
{
    private static readonly HashSet<Type> NumericTypes = new HashSet<Type>
    {
        typeof(int),  typeof(double),  typeof(decimal),
        typeof(long), typeof(short),   typeof(sbyte),
        typeof(byte), typeof(ulong),   typeof(ushort),  
        typeof(uint), typeof(float)
    };

    public static bool IsNumeric(Type myType)
    {
       return NumericTypes.Contains(Nullable.GetUnderlyingType(myType) ?? myType);
    }
}

9

Підхід, заснований на пропозиції Філіпа , покращений внутрішнім типом перевірки SFun28 для Nullableтипів:

public static class IsNumericType
{
    public static bool IsNumeric(this Type type)
    {
        switch (Type.GetTypeCode(type))
        {
            case TypeCode.Byte:
            case TypeCode.SByte:
            case TypeCode.UInt16:
            case TypeCode.UInt32:
            case TypeCode.UInt64:
            case TypeCode.Int16:
            case TypeCode.Int32:
            case TypeCode.Int64:
            case TypeCode.Decimal:
            case TypeCode.Double:
            case TypeCode.Single:
                return true;
            case TypeCode.Object:
                if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
                {
                    return Nullable.GetUnderlyingType(type).IsNumeric();
                    //return IsNumeric(Nullable.GetUnderlyingType(type));
                }
                return false;
            default:
                return false;
        }
    }
}

Чому це? Я повинен був перевірити, чи є даний Type typeчисловий тип, а не чи довільний object oчисельний.


4

Завдяки C # 7 цей метод дає мені кращі показники, ніж корпус увімкнення TypeCodeта HashSet<Type>:

public static bool IsNumeric(this object o) => o is byte || o is sbyte || o is ushort || o is uint || o is ulong || o is short || o is int || o is long || o is float || o is double || o is decimal;

Тести наступні:

public static class Extensions
{
    public static HashSet<Type> NumericTypes = new HashSet<Type>()
    {
        typeof(byte), typeof(sbyte), typeof(ushort), typeof(uint), typeof(ulong), typeof(short), typeof(int), typeof(long), typeof(decimal), typeof(double), typeof(float)
    };

    public static bool IsNumeric1(this object o) => NumericTypes.Contains(o.GetType());

    public static bool IsNumeric2(this object o) => o is byte || o is sbyte || o is ushort || o is uint || o is ulong || o is short || o is int || o is long || o is decimal || o is double || o is float;

    public static bool IsNumeric3(this object o)
    {
        switch (o)
        {
            case Byte b:
            case SByte sb:
            case UInt16 u16:
            case UInt32 u32:
            case UInt64 u64:
            case Int16 i16:
            case Int32 i32:
            case Int64 i64:
            case Decimal m:
            case Double d:
            case Single f:
                return true;
            default:
                return false;
        }
    }

    public static bool IsNumeric4(this object o)
    {
        switch (Type.GetTypeCode(o.GetType()))
        {
            case TypeCode.Byte:
            case TypeCode.SByte:
            case TypeCode.UInt16:
            case TypeCode.UInt32:
            case TypeCode.UInt64:
            case TypeCode.Int16:
            case TypeCode.Int32:
            case TypeCode.Int64:
            case TypeCode.Decimal:
            case TypeCode.Double:
            case TypeCode.Single:
                return true;
            default:
                return false;
        }
    }
}

class Program
{
    static void Main(string[] args)
    {           
        var count = 100000000;

        //warm up calls
        for (var i = 0; i < count; i++)
        {
            i.IsNumeric1();
        }
        for (var i = 0; i < count; i++)
        {
            i.IsNumeric2();
        }
        for (var i = 0; i < count; i++)
        {
            i.IsNumeric3();
        }
        for (var i = 0; i < count; i++)
        {
            i.IsNumeric4();
        }

        //Tests begin here
        var sw = new Stopwatch();
        sw.Restart();
        for (var i = 0; i < count; i++)
        {
            i.IsNumeric1();
        }
        sw.Stop();

        Debug.WriteLine(sw.ElapsedMilliseconds);

        sw.Restart();
        for (var i = 0; i < count; i++)
        {
            i.IsNumeric2();
        }
        sw.Stop();

        Debug.WriteLine(sw.ElapsedMilliseconds);

        sw.Restart();
        for (var i = 0; i < count; i++)
        {
            i.IsNumeric3();
        }
        sw.Stop();

        Debug.WriteLine(sw.ElapsedMilliseconds);

        sw.Restart();
        for (var i = 0; i < count; i++)
        {
            i.IsNumeric4();
        }
        sw.Stop();

        Debug.WriteLine(sw.ElapsedMilliseconds);
    }

3

Ви можете використовувати Type.IsPrimitive, а потім розібратися Booleanта Charтипи, приблизно так:

bool IsNumeric(Type type)
{
    return type.IsPrimitive && type!=typeof(char) && type!=typeof(bool);
}

EDIT : Ви можете також виключити типи IntPtrта UIntPtrтипи, якщо ви не вважаєте їх числовими.


1
Отже, decimalтип не є числовим?
ЛукаH

Ой ... ну, здається, рішення Гійом найкраще зрештою.
Конаміман

3

Розширення типу з підтримкою нульового типу.

public static bool IsNumeric(this Type type)
    {
        if (type == null) { return false; }

        if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
        {
            type = type.GetGenericArguments()[0];
        }

        switch (Type.GetTypeCode(type))
        {
            case TypeCode.Byte:
            case TypeCode.SByte:
            case TypeCode.UInt16:
            case TypeCode.UInt32:
            case TypeCode.UInt64:
            case TypeCode.Int16:
            case TypeCode.Int32:
            case TypeCode.Int64:
            case TypeCode.Decimal:
            case TypeCode.Double:
            case TypeCode.Single:
                return true;
            default:
                return false;
        }
    }

1

Коротка відповідь: Ні.

Більш довгий відповідь: Ні.

Справа в тому, що багато різних типів в C # можуть містити числові дані. Якщо ви не знаєте, чого очікувати (Int, Double та ін.), Вам потрібно використовувати "довгий" випадок справи.


1

Це також може працювати. Однак, можливо, ви захочете виконати його з Type.Parse, щоб подати його так, як вам згодом.

public bool IsNumeric(object value)
{
    float testValue;
    return float.TryParse(value.ToString(), out testValue);
}

1

Модифікований Скит і рішення arviman у використанні Generics, Reflectionі C# v6.0.

private static readonly HashSet<Type> m_numTypes = new HashSet<Type>
{
    typeof(int),  typeof(double),  typeof(decimal),
    typeof(long), typeof(short),   typeof(sbyte),
    typeof(byte), typeof(ulong),   typeof(ushort),
    typeof(uint), typeof(float),   typeof(BigInteger)
};

Слідкує за:

public static bool IsNumeric<T>( this T myType )
{
    var IsNumeric = false;

    if( myType != null )
    {
        IsNumeric = m_numTypes.Contains( myType.GetType() );
    }

    return IsNumeric;
}

Використання для (T item):

if ( item.IsNumeric() ) {}

null повертає помилкове.


1

Перемикання відбувається трохи повільно, тому що кожного разу методи в найгіршій ситуації будуть проходити через усі типи. Я думаю, що використовувати Dictonary приємніше, у цій ситуації вам доведеться O(1):

public static class TypeExtensions
{
    private static readonly HashSet<Type> NumberTypes = new HashSet<Type>();

    static TypeExtensions()
    {
        NumberTypes.Add(typeof(byte));
        NumberTypes.Add(typeof(decimal));
        NumberTypes.Add(typeof(double));
        NumberTypes.Add(typeof(float));
        NumberTypes.Add(typeof(int));
        NumberTypes.Add(typeof(long));
        NumberTypes.Add(typeof(sbyte));
        NumberTypes.Add(typeof(short));
        NumberTypes.Add(typeof(uint));
        NumberTypes.Add(typeof(ulong));
        NumberTypes.Add(typeof(ushort));
    }

    public static bool IsNumber(this Type type)
    {
        return NumberTypes.Contains(type);
    }
}

1

Спробуйте TypeSupport NuGet пакет для C #. Він має підтримку для виявлення всіх числових типів (серед багатьох інших функцій):

var extendedType = typeof(int).GetExtendedType();
Assert.IsTrue(extendedType.IsNumericType);

Я не знав цього пакету. Здається, рятувальник життя в багатьох випадках уникає написання власного коду для операцій, про які вимагає ОП. Дякую !
AFAct

0

На жаль, ці типи не мають багато спільного, крім всіх типів цінності. Але, щоб уникнути тривалого переключення випадків, ви можете просто визначити список, який читається лише з усіма цими типами, а потім просто перевірити, чи вказаний тип знаходиться у списку.


0

Всі вони мають цінні типи (крім bool та, можливо, перерахунків). Таким чином, ви можете просто використовувати:

bool IsNumberic(object o)
{
    return (o is System.ValueType && !(o is System.Boolean) && !(o is System.Enum))
}

1
Це повернеться правдою для будь-яких визначених користувачем struct... Я не думаю, що саме цього ви хочете.
Дан Тао

1
Ви праві. Вбудовані числові типи також є конструкціями. Тож краще піти з примітивним порівнянням тоді.
MandoMando

0

EDIT: Ну, я змінив код нижче, щоб бути більш ефективним, а потім запустив тести, опубліковані @Hugo проти нього. Швидкість приблизно нарівнюється із IF IF @ Hugo, використовуючи останній елемент у його послідовності (Десяткові). Якщо, однак, використовуючи перший пункт "байт", він бере торт, але чітко впорядковує питання, коли справа доходить до виконання. Хоча використання наведеного нижче коду простіше писати та більш послідовно щодо його вартості, однак це не є ремонтом чи можливістю в майбутньому.

Схоже, перехід з Type.GetTypeCode () на Convert.GetTypeCode () значно збільшив продуктивність, приблизно на 25%, VS Enum.Parse (), що було в 10 разів повільніше.


Я знаю, що ця публікація стара, але якщо за допомогою методу перерахування TypeCode найпростіший (і, мабуть, найдешевший) був би щось подібне:

public static bool IsNumericType(this object o)
{   
  var t = (byte)Convert.GetTypeCode(o);
  return t > 4 && t < 16;
}

З огляду на таке визначення enum для TypeCode:

public enum TypeCode
{
    Empty = 0,
    Object = 1,
    DBNull = 2,
    Boolean = 3,
    Char = 4,
    SByte = 5,
    Byte = 6,
    Int16 = 7,
    UInt16 = 8,
    Int32 = 9,
    UInt32 = 10,
    Int64 = 11,
    UInt64 = 12,
    Single = 13,
    Double = 14,
    Decimal = 15,
    DateTime = 16,
    String = 18
}

Я не перевірив її ретельно, але для основних числових типів C # це, мабуть, охоплює це. Однак, як згадував @JonSkeet, цей перелік не оновлюється для додаткових типів, доданих до .NET вниз.


-1

ой! Непрочитайте питання! Особисто, котиться зі Скетом .


HRM, звучить , як ви хочете , щоб DoSomethingна Typeваших даних. Що ви можете зробити, це наступне

public class MyClass
{
    private readonly Dictionary<Type, Func<SomeResult, object>> _map = 
        new Dictionary<Type, Func<SomeResult, object>> ();

    public MyClass ()
    {
        _map.Add (typeof (int), o => return SomeTypeSafeMethod ((int)(o)));
    }

    public SomeResult DoSomething<T>(T numericValue)
    {
        Type valueType = typeof (T);
        if (!_map.Contains (valueType))
        {
            throw new NotSupportedException (
                string.Format (
                "Does not support Type [{0}].", valueType.Name));
        }
        SomeResult result = _map[valueType] (numericValue);
        return result;
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.