C # - как определить, является ли тип числом


105

Есть ли способ определить, является ли данный тип .Net числом? Например: System.UInt32/UInt16/Doubleвсе числа. Я хочу избежать длинного переключателя на Type.FullName.


4
Обманывать многих, многих, многих. Почему это еще не закрыли?
— Noldorin

2
Дубликат stackoverflow.com/questions/1130698 и очень близок к некоторым другим.
— Хенк Холтерман

Ответы:


110

Попробуй это:

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

Примитивные типы: Boolean, Byte, 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тип не числовой?
— LukeH

2
@Xaero: Я не сомневаюсь, что decimal это числовое значение. То, что это не примитив, не означает, что он не числовой. Ваш код должен это учитывать.
— LukeH

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), ...
};

РЕДАКТИРОВАТЬ: Одно из преимуществ этого по сравнению с использованием кода типа заключается в том, что когда в .NET вводятся новые числовые типы (например, BigInteger и Complex ), их легко настраивать, тогда как эти типы не получат код типа.


4
и как бы вы использовали HashSet?
— RvdK

8
NumericTypes.Contains (что угодно)?
— mqp

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

Можно было бы подумать, что компилятор выполнит неявное преобразование оператора switch в hashset.
— Рольф Кристенсен

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));
    }

Я знаю, что могу просто добавить сами значения NULL в свой HashSet. Но это решение позволяет избежать опасности забыть добавить в список конкретный Nullable.

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

2
Действительно ли тип, допускающий значение NULL, числовой? Насколько мне известно, нуль не является числом.
— IllidanS4 хочет вернуть Монику

2
Это зависит от того, чего вы хотите достичь. В моем случае мне тоже нужно было включить значения NULL. Но я также мог подумать о ситуациях, когда это нежелательное поведение.
— Юрген Штайнблок

Хорошо! Обработка числа, допускающего значение NULL, как числа очень полезно при проверке ввода пользовательского интерфейса.
— guogangj

1
@ IllidanS4 Проверяется Тип, а не значение. В большинстве случаев числовые типы, допускающие значение NULL, следует рассматривать как числовые. Конечно, если проверка проводилась по значению, а значение равно нулю, тогда да, его нельзя считать числовым.
— 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, сгенерированный таким переключателем, и заметил, что компилятор уже применяет оптимизацию (в 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 этот метод дает мне лучшую производительность, чем включение case 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тип не числовой?
— LukeH

Ой ... ну, кажется, решение Гийома все-таки лучшее.
— Konamiman

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 и т. Д.), Вам нужно использовать «длинный» оператор case.


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 возвращает false.


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

Попробуйте пакет Nuget TypeSupport для C #. Он поддерживает определение всех числовых типов (среди многих других функций):

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

Я не знал этого пакета. Во многих случаях кажется спасителем жизни, чтобы избежать написания собственного кода для операций, запрашиваемых OP. Спасибо !
— AFract

0

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


0

Все они являются типами значений (кроме bool и, возможно, enum). Итак, вы можете просто использовать:

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

1
Это вернет истину для любого определенного пользователем struct... Я не думаю, что вы этого хотите.
— Дэн Тао

1
Ты прав. Встроенные числовые типы также являются структурами. Так что лучше пойти с примитивным сравнением.
— MandoMando

0

РЕДАКТИРОВАТЬ: Ну, я изменил приведенный ниже код, чтобы он был более производительным, а затем запустил тесты, опубликованные @Hugo, против него. Скорости примерно такие же, как у @Hugo IF, используя последний элемент в его последовательности (Decimal). Если, однако, использовать первый элемент «байт», то он выигрывает, но четкий порядок имеет значение, когда дело доходит до производительности. Хотя использовать приведенный ниже код проще для написания и более последовательным по стоимости, он, тем не менее, не подлежит обслуживанию или будущей проверке.

Похоже, переключение с 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;
}

Учитывая следующее определение перечисления для 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.