Проверка, является ли объект числом в C#

Я хотел бы проверить, является ли объект числом, чтобы .ToString() приведет к строке, содержащей цифры и +,-,.

Возможно ли это путем простой проверки типов в.net (например: if (p is Number))?

Или я должен преобразовать в строку, а затем попробовать разбор в два раза?

Обновление: чтобы уточнить, что мой объект это int, uint, float, double, и так далее, это не строка. Я пытаюсь сделать функцию, которая сериализует любой объект в XML, как это:

<string>content</string>

или же

<numeric>123.3</numeric>

или поднять исключение.

15 ответов

Решение

Вам просто нужно будет выполнить проверку типов для каждого из основных числовых типов.

Вот метод расширения, который должен делать эту работу:

public static bool IsNumber(this object value)
{
    return value is sbyte
            || value is byte
            || value is short
            || value is ushort
            || value is int
            || value is uint
            || value is long
            || value is ulong
            || value is float
            || value is double
            || value is decimal;
}

Это должно охватывать все числовые типы.

Обновить

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

string value = "123.3";
double num;
if (!double.TryParse(value, out num))
    throw new InvalidOperationException("Value is not a number.");

Конечно, это не будет обрабатывать очень большие целые / длинные десятичные числа, но если это так, вам просто нужно добавить дополнительные вызовы long.TryParse / decimal.TryParse / что угодно.

Взято из блога Скотта Хансельмана:

public static bool IsNumeric(object expression)
{
    if (expression == null)
    return false;

    double number;
    return Double.TryParse( Convert.ToString( expression
                                            , CultureInfo.InvariantCulture)
                          , System.Globalization.NumberStyles.Any
                          , NumberFormatInfo.InvariantInfo
                          , out number);
}

Воспользуйтесь преимуществом свойства IsPrimitive, чтобы создать удобный метод расширения:

public static bool IsNumber(this object obj)
{
    if (Equals(obj, null))
    {
        return false;
    }

    Type objType = obj.GetType();
    objType = Nullable.GetUnderlyingType(objType) ?? objType;

    if (objType.IsPrimitive)
    {
        return objType != typeof(bool) && 
            objType != typeof(char) && 
            objType != typeof(IntPtr) && 
            objType != typeof(UIntPtr);
    }

    return objType == typeof(decimal);
}

РЕДАКТИРОВАТЬ: Исправлено в соответствии с комментариями. Обобщения были удалены, так как.GetType() содержит типы значений. Также включено исправление для значений Nullable.

Вместо того, чтобы бросать свой собственный, самый надежный способ определить, является ли встроенный тип числовым, - это, вероятно, ссылка Microsoft.VisualBasic и позвонить Information.IsNumeric(object value), Реализация обрабатывает ряд тонких случаев, таких как char[] и строки HEX и OCT.

Есть несколько отличных ответов выше. Вот решение "все в одном". Три перегрузки для разных обстоятельств.

// Extension method, call for any object, eg "if (x.IsNumeric())..."
public static bool IsNumeric(this object x) { return (x==null ? false : IsNumeric(x.GetType())); }

// Method where you know the type of the object
public static bool IsNumeric(Type type) { return IsNumeric(type, Type.GetTypeCode(type)); }

// Method where you know the type and the type code of the object
public static bool IsNumeric(Type type, TypeCode typeCode) { return (typeCode == TypeCode.Decimal || (type.IsPrimitive && typeCode != TypeCode.Object && typeCode != TypeCode.Boolean && typeCode != TypeCode.Char)); }

Вы можете использовать такой код:

if (n is IConvertible)
  return ((IConvertible) n).ToDouble(CultureInfo.CurrentCulture);
else
  // Cannot be converted.

Если ваш объект Int32, Single, Double и т.д. он будет выполнять преобразование. Кроме того, строка реализует IConvertible но если строка не может быть преобразована в двойной, то FormatException будет брошен.

Предполагая, что ваш ввод является строкой...

Есть 2 способа:

использовать Double.TryParse()

double temp;
bool isNumber = Double.TryParse(input, out temp);

использовать Regex

 bool isNumber = Regex.IsMatch(input,@"-?\d+(\.\d+)?");

Там есть три разных понятия:

  • чтобы проверить, является ли это число (то есть само (обычно в штучной упаковке) числовым значением), проверьте тип с is - например if(obj is int) {...}
  • проверить, может ли строка быть проанализирована как число; использование TryParse()
  • но если объект не является числом или строкой, но вы подозреваете, ToString() может дать что-то похожее на число, а затем позвонить ToString() и рассматривать это как строку

В обоих первых двух случаях вам, вероятно, придется обрабатывать отдельно каждый числовой тип, который вы хотите поддерживать (double / decimal / int) - у каждого разные диапазоны и точность, например.

Вы также можете посмотреть на регулярные выражения для быстрой грубой проверки.

В .NET 7 был представлен новый API для поддержки универсальной математики , который предлагает множество интерфейсов, реализованных во всех числовых типах. Есть интерфейсINumber<TSelf>который можно использовать в качестве ограничения типа для универсальных методов, чтобы разрешить только числовые типы (, , ,...):

      public void FooBar<T>(T number) where T : INumber<T>
{
   // ...number is a numeric type.
}

Существуют также более детализированные интерфейсы для категорий числовых типов:

  • IBinaryInteger<T>дляbyte,short,int,long, ...
  • IFloatingPoint<T>дляfloat,double,decimal, ...
  • ...

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


Если вы должны полагаться наobject, вы можете по крайней мере использовать новый интерфейс, чтобы проверить с помощью отражения, является ли он числовым типом. Вы бы проверили, реализует ли тип какой-либо конкретныйINumber<T>интерфейса, сравнивая их общие определения типов с открытыми универсальнымиINumber<>тип. Имейте в виду, что отражение может повлечь за собой затраты на производительность, которые необходимо учитывать.

      private bool IsNumericType(object obj)
{
   return obj
      .GetType()
      .GetInterfaces()
      .Any(@interface => @interface.GetGenericTypeDefinition() == typeof(INumber<>));
}

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

Пока я пишу свой object.IsNumeric() метод расширения, основанный на ответе Сола Долгина на этот вопрос. Я столкнулся с потенциальной проблемой, заключающейся в том, что вы получите OverflowException если вы попробуете это с double.MaxValue или double.MinValue.

Мое "решение" заключалось в том, чтобы объединить принятый ответ от Нолдорина с ответом Саула Долгина и добавить переключатель сопоставления с образцом, прежде чем пытаться что-либо проанализировать (и использовать некоторую доброту C#7, чтобы немного привести в порядок):

public static bool IsNumeric(this object obj)
{
    if (obj == null) return false;

    switch (obj)
    {
        case sbyte _: return true;
        case byte _: return true;
        case short _: return true;
        case ushort _: return true;
        case int _: return true;
        case uint _: return true;
        case long _: return true;
        case ulong _: return true;
        case float _: return true;
        case double _: return true;
        case decimal _: return true;
    }

    string s = Convert.ToString(obj, CultureInfo.InvariantCulture);

    return double.TryParse(s, NumberStyles.Any, NumberFormatInfo.InvariantInfo, out double _);
}

Если ваше требование действительно

.ToString() приведет к строке, содержащей цифры и +,-,.

и вы хотите использовать double.TryParse, тогда вам нужно использовать перегрузку, которая принимает параметр NumberStyles, и убедиться, что вы используете инвариантную культуру.

Например, для числа, которое может иметь начальный знак, без начального или конечного пробела, без разделителя тысяч и десятичного разделителя периода, используйте:

NumberStyles style = 
   NumberStyles.AllowLeadingSign | 
   NumberStyles.AllowDecimalPoint | 
double.TryParse(input, style, CultureInfo.InvariantCulture, out result);

Да, это работает:

object x = 1;
Assert.That(x is int);

Для числа с плавающей запятой вы должны будете проверить, используя тип с плавающей запятой:

object x = 1f;
Assert.That(x is float);

Не забывайте, что .NET включает в себя Microsoft.VisualBasic.Information.IsNumeric() , который можно использовать из C# точно так же, как и из VB. Это может быть предпочтительнее написания собственного или, по крайней мере, использовать его в качестве основы.


Это работает следующим образом: сначала ищутся определенные типы, которые могут быть «преобразованы» в числа или проанализированы как числа (строки, символы или массивы символов), а затем, наконец, просто проверяется код типа, чтобы увидеть, является ли он одним из встроенных числовых значений. типы.

Если вы хотите исключить строки или символы, вы можете использовать это так:

      if (foo.GetType().IsValueType && !(foo is char) && Information.IsNumeric(foo))
{
    // Numeric...
}

Справочный источник, похоже, не включает это, но его можно просмотреть в Visual Studio:

      public static bool IsNumeric(object Expression)
{
    IConvertible convertible = Expression as IConvertible;
    if (convertible == null)
    {
        char[] array = Expression as char[];
        if (array == null)
        {
            return false;
        }

        Expression = new string(array);
    }

    TypeCode typeCode = convertible.GetTypeCode();
    if (typeCode == TypeCode.String || typeCode == TypeCode.Char)
    {
        string value = convertible.ToString(null);
        try
        {
            long i64Value = default(long);
            if (Utils.IsHexOrOctValue(value, ref i64Value))
            {
                return true;
            }
        }
        catch (StackOverflowException ex) { throw ex; }
        catch (OutOfMemoryException ex2) { throw ex2; }
        catch (ThreadAbortException ex3) { throw ex3; }
        catch (Exception) { return false; }

        double Result = default(double);
        return DoubleType.TryParse(value, ref Result);
    }

    return IsOldNumericTypeCode(typeCode);
}

То, как он проверяет явные числовые типы, похоже на ответ /questions/38163542/proverka-yavlyaetsya-li-obekt-chislom-v-c/38163560#38163560 , хотя его логика инвертируется таким образом, который может быть более надежным/безопасным: он возвращает толькоtrueдля известного числового типа, в отличие от возвратаfalseдля известных нечисловых типов; последнее может привести к ошибке, если будут добавлены другие типы.

      internal static bool IsOldNumericTypeCode(TypeCode TypCode)
{
    switch (TypCode)
    {
        case TypeCode.Boolean:
        case TypeCode.Byte:
        case TypeCode.Int16:
        case TypeCode.Int32:
        case TypeCode.Int64:
        case TypeCode.Single:
        case TypeCode.Double:
        case TypeCode.Decimal:
            return true;
        default:
            return false;
    }
}

Вот еще более короткая версия отличного ответа @Noldorin:

      public static Boolean IsNumeric(this Object obj) =>
  obj is SByte or Byte or Int16 or UInt16 or Int32 or UInt32 or Int64 or UInt64 or Single or Double or Decimal;

Вы можете выполнить проверку по типу. Получите тип с помощью оператора typeof().

      private bool IsNumber(Type type)
{
   return type.IsPrimitive && type.IsValueType;
}
Другие вопросы по тегам