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

91

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

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

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

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

<string>content</string>

или

<numeric>123.3</numeric>

или создать исключение.

Петр Чапла
источник
5
Похоже, вы пытаетесь написать свой собственный XmlSerializer - что не так с одним поставщиком .NET- msdn.microsoft.com/en-us/library/… ?
RichardOD
2
Возможно, вы сможете обойти всю эту проблему, определив свой формат XML с помощью XSD, а затем создав объект, в который вы можете сериализовать свои данные с помощью поставляемого инструмента XSD - msdn.microsoft.com/en-us/library/x6c1kb0s % 28VS.71% 29.aspx
Декстер,
@RichardOD: Могу ли я использовать сериализацию xml для сериализации объекта []? Мне нужно, чтобы он вызвал функцию Flash adobe.com/livedocs/flex/201/html/wwhelp/wwhimpl/common/html/…
Петр Чапла

Ответы:

183

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

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

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/ чему-либо еще.

Нолдорин
источник
Мой объект - int, short, uint, float, double или что-то еще, что является числом
Петр Чапла
@Piotr: Ах да. Кажется, я вас неправильно понял. Смотрите мой обновленный ответ.
Noldorin
1
@Noldorin: на самом деле ваша предыдущая версия кода тоже подойдет; просто добавьте нулевую проверку и используйте value.ToString (). Тогда вам не нужно проверять все числовые типы.
Fredrik Mörk
1
@Noldorin Печально, что верное решение - это многословие :(.
Петр Чапла
1
@Joe: На самом деле, это не имеет значения, поскольку ToString также будет использовать текущую культуру.
Noldorin
36

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

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);
}
Саул Долгин
источник
6
Проблема с этим подходом заключается в том, что если вы передадите строку, которая выглядит как число, она отформатирует ее. Для большинства это могло быть приемлемо, но для меня это было препятствием.
Роб Седжвик
1
Еще одна потенциальная проблема заключается в том, что вы не можете анализировать минимальные / максимальные значения для double. double.Parse(double.MaxValue.ToString())вызывает OverflowException. Вы можете исправить это, указав .ToString("R")в этом случае модификатор туда и обратно , но эта перегрузка недоступна, Convert.ToString(...)поскольку мы не знаем тип. Я знаю, что это немного второстепенный случай, но я наткнулся на него, когда писал тесты для своего собственного .IsNumeric()расширения. Мое «решение» заключалось в том, чтобы добавить переключатель проверки типов перед тем, как что-либо анализировать, см. Мой ответ на этот вопрос для кода.
Бен
21

Воспользуйтесь свойством 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 (). Также включено исправление для значений, допускающих значение NULL.

Кенан Э.К.
источник
1
Часть дженериков здесь не дает вам ничего лишнего, не так ли? вы получаете доступ только к GetType (), который доступен для объекта ...
Питер Лиллеволд,
Он сохраняет одну операцию с ящиком, если вызывается для типа значения. Подумайте о возможности повторного использования.
Kenan EK
1
Почему бы не использовать typeof (T) вместо obj.GetType, так вы не получите исключение NullReferenceException, если кто-то передает нулевой ссылочный тип. Вы также можете наложить на T общее ограничение, чтобы принимать только типы значений. Конечно, если вы это сделаете, во время компиляции у вас появится много информации.
Trillian
objectи stringне являются примитивными типами.
We All Monica
@jnylen: этот ответ был довольно давно. Я думаю, что в то время я кое-что откопал из рефлексивного источника фреймворка, но кто может сказать сегодня ... Фиксированный ответ.
Kenan EK
10

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

// 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)); }
Мик Бруно
источник
рассмотрите возможность добавления нулевой проверки
wiero
На самом деле не нужна проверка на null - как метод расширения вы не можете вызвать его с нулевым значением. Конечно, кто-то все еще может вызывать как обычную функцию, но это не ожидаемое использование метода расширения.
Мик Бруно
5
Я думаю, это можно назвать нулевым значением. объект obj = null; obj.IsNumeric ();
wiero
Спасибо, Вейро, исправили. Не понимал, что вызов метода расширения с нулевым значением возможен, но, конечно, это так!
Мик Бруно
Я думаю, что в первой перегрузке отсутствует скобка в конце: «return (x == null? False: IsNumeric (x.GetType ()));»
Гленн Гарсон
8

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

сатнхак
источник
Это должно быть наверху!
nawfal
4

Здесь есть три разных концепции:

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

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

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

Марк Гравелл
источник
4

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

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

используйте Double.TryParse ()

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

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

 bool isNumber = Regex.IsMatch(input,@"-?\d+(\.\d+)?");
Филипп Лейберт
источник
4

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

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

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

Мартин Ливерсаж
источник
Фактически строки будут проанализированы, но если они не в правильном формате, выбрасывается FormatException.
alfoks
@alfoks: Вы абсолютно правы, поэтому я обновил ответ.
Martin Liversage
1

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

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

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

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

NumberStyles style = 
   NumberStyles.AllowLeadingSign | 
   NumberStyles.AllowDecimalPoint | 
double.TryParse(input, style, CultureInfo.InvariantCulture, out result);
Джо
источник
1

При написании моего собственного 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 _);
}
Бен
источник
Остерегайтесь типов, допускающих значение NULL.
Гильермо Пранди,
0

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

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

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

object x = 1f;
Assert.That(x is float);
Питер Лиллевольд
источник
Это будет работать, если объект был int до неявного или явного приведения к объекту. В вашем примере магическое число 1 - это int, а затем неявное преобразование в тип переменной x .. Если бы вы сделали объект x = 1.0, ваше утверждение вернуло бы false.
Dexter
Есть числа, которые не являются целыми числами.
Fredrik Mörk
да, так что моя точка зрения в основном заключается в том, что @Noldorin имеет в своем ответе прямо сейчас.
Питер Лиллеволд,