检查对象是否为数字

114

我想要检查一个对象是否为数字,以便.ToString()会产生一个包含数字、+-.的字符串。

在.NET中简单类型检查是否可能?像这样:

if (p is Number)
或者我应该先将其转换为字符串,然后尝试解析成双精度浮点数吗?
更新:澄清一下,我的对象是int、uint、float、double等,不是字符串。 我正在尝试编写一个函数,将任何对象序列化为如下的XML格式:
<string>content</string>
或者
<numeric>123.3</numeric>

或引发异常。


5
听起来你正在尝试编写自己的XmlSerializer-那么.NET提供的这个有什么问题吗-http://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlserializer.aspx? - RichardOD
2
你可以通过使用XSD定义你的XML格式,然后使用附带的XSD工具创建一个对象,将数据序列化到该对象中,从而绕过整个问题。- http://msdn.microsoft.com/en-us/library/x6c1kb0s%28VS.71%29.aspx - Dexter
@RichardOD:我能使用XML序列化来序列化object[]吗?我需要它来调用Flash函数https://www.adobe.com/livedocs/flex/201/html/wwhelp/wwhimpl/common/html/wwhelp.htm?context=LiveDocs_Book_Parts&file=19_External_Interface_178_3.html#126216 - Piotr Czapla
15个回答

209

你只需要为每个基本数值类型进行一次类型检查即可。

这是一个扩展方法,应该可以完成工作:

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 / 其他方法。

1
@Noldorin:实际上你之前的代码版本也可以工作;只需添加一个空值检查并使用 value.ToString()。然后你就不需要检查所有数值类型了。 - Fredrik Mörk
@Fredrik:没错。我猜...阅读起来可能会稍微短一些,但我认为你会同意这是一个小技巧。当然,它肯定效率要低得多。 - Noldorin
1
@Noldorin 很遗憾,正确的解决方案是如此冗长 :(. - Piotr Czapla
1
@Joe:实际上,这并没有什么区别,因为ToString也会使用当前的区域设置。 - Noldorin
我很喜欢你的回答,但是我仍然更喜欢Java实现,因为它可以让所有数字都实现Number接口。谢谢(+1)。 - Andez
显示剩余5条评论

39

以下内容摘自Scott Hanselman的博客

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

7
这种方法的问题在于,如果你传递一个看起来像数字的字符串,它会对其进行格式化。这可能对大多数人来说还可以,但对我来说却是个致命问题。 - Rob Sedgwick
2
这种方法的另一个潜在问题是,您无法解析double的最小/最大值。double.Parse(double.MaxValue.ToString())会导致OverflowException。在这种情况下,您可以通过提供往返修饰符.ToString("R")来解决此问题,但是对于Convert.ToString(...),该重载不可用,因为我们不知道类型。我知道这有点边缘化,但我在为自己的.IsNumeric()扩展编写测试时遇到了这个问题。我的“解决方案”是在尝试解析任何内容之前添加类型检查开关,请参见此问题的答案中的代码。 - Ben

22

利用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()`将值类型装箱,因此已删除泛型。还包括针对可空值的修复。


1
泛型部分在这里并没有给你带来任何额外的东西,对吧?你只是访问了object上可用的GetType()方法。 - Peter Lillevold
1
为什么不使用typeof(T)而不是obj.GetType,这样如果有人传递一个空引用类型,就不会出现NullReferenceException。您还可以对T设置通用约束,以仅接受值类型。当然,如果这样做,您在编译时开始拥有大量信息。 - Trillian
objectstring不是原始类型。 - We Are All Monica
@jnylen:这个答案是相当久以前的了。我相信当时我从反射框架源代码中挖掘出了一些东西,但今天谁能说得准呢...已修正答案。 - Kenan E. K.
死灵法师在此:FYI,无需检查“Decimal”,因为原始形式是“Double”和“Short”,如MSDN所述。否则+1,帮助我构建了一个ObjectExtension方法,而不是为每种类型都创建一个方法。 - GoldBishop
显示剩余3条评论

11

以上有一些很好的答案。这里是一个全能解决方案,有三个重载用于不同情况。

// 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
不需要进行空值检查 - 作为扩展方法,您无法使用空值调用它。当然,某些人仍然可以将其作为普通函数调用,但这不是扩展方法的预期用法。 - Mick Bruno
6
我认为可以称之为空值。对象 obj = null; obj.IsNumeric(); - wiero
1
谢谢Weiro,已经修复了。没想到可以使用空值调用扩展方法,但当然是可以的! - Mick Bruno
我认为第一个重载函数末尾缺少一个括号:"return (x==null ? false : IsNumeric(x.GetType()));"。 - glenn garson
我认为在第三个函数中假设如果对象不是其中之一,那么它就是数字有点冒险。如果将来的.NET包括新的非数字原语,这可能会产生错误的结果。与内置的VB.NET IsNumeric相比,它反转了逻辑(或多或少)。https://dev59.com/y3NA5IYBdhLWcg3wAIzd#74044011 - StayOnTarget

11

与其自己开发,判断内置类型是否为数字的最可靠方法可能是引用Microsoft.VisualBasic并调用Information.IsNumeric(object value)。该实现处理了许多微妙的情况,如char[]和HEX和OCT字符串。


1
这应该在顶部! - nawfal
如果它能正常运行那就太好了。在我的.NET Framework 4.7.2中,它无法解析UInt16,尽管帮助文档说应该可以。检查了Microsoft.VisualBasic.dll的反汇编代码-如果输入是字符串,则尝试将其转换为double类型,否则调用IsOldNumericTypeCode并检查Int16和其他一些类型,但忽略了UInt16和其他无符号类型。 - sarh
那么最可靠的方法是调用VisualBasic吗? - E. van Putten
1
@E.vanPutten 今天我不会使用这个。 - satnhak

6
您可以使用以下代码:

您可以使用以下代码:

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

如果您的对象是Int32SingleDouble等,则会执行转换。此外,字符串实现了IConvertible,但如果字符串无法转换为double,则会抛出FormatException

实际上,字符串将会被解析,但如果格式不正确,将抛出 FormatException 异常。 - alfoks
@alfoks:你说得完全正确,所以我已经更新了答案。 - Martin Liversage

4

在撰写基于Saul Dolgin答案的object.IsNumeric()扩展方法时,我发现一个潜在问题,如果您尝试使用double.MaxValuedouble.MinValue,您将获得OverflowException

我的“解决方案”是将Noldorin的答案与Saul Dolgin的答案相结合,并在尝试解析任何内容之前添加模式匹配开关(并使用一些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 _);
}

注意可空类型。 - Guillermo Prandi

4
假设您的输入是一个字符串...
有两种方法:
1. 使用 Double.TryParse() 2. 使用 Convert.ToDouble()
double temp;
bool isNumber = Double.TryParse(input, out temp);

使用正则表达式

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

4

这里有三个不同的概念:

  • 检查它是否是一个数字(即一个(通常装箱的)数值本身),使用is检查类型,例如 if(obj is int) {...}
  • 检查一个字符串是否可以解析为数字; 使用TryParse()
  • 但如果对象既不是数字也不是字符串,但您认为ToString()可能会返回像数字一样的内容,则需要调用ToString()并将其视为字符串处理。

在前两种情况下,您可能需要单独处理每种要支持的数字类型(double/decimal/int)- 每种类型具有不同的范围和精度,例如。

您还可以使用正则表达式进行快速粗略检查。


2
在.NET 7中引入了一个新的API来支持通用数学,它提供了许多接口,这些接口在所有数字类型中都得到了实现。有一个接口INumber<TSelf>,可以用作泛型方法的类型约束,只允许使用数字类型(intdoubledecimal等):
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


如果你必须依赖于object,那么你至少可以使用新的INumber<T>接口通过反射检查它是否是数字类型。你可以通过将它们的泛型类型定义与开放泛型INumber<>类型进行比较,来检查该类型是否实现了任何具体的INumber<T>接口。请注意,反射可能会产生性能成本,必须加以考虑。
private bool IsNumericType(object obj)
{
   return obj
      .GetType()
      .GetInterfaces()
      .Any(@interface => @interface.GetGenericTypeDefinition() == typeof(INumber<>));
}

API可能会为您的目的提供其他有用的界面,例如格式化和解析


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接