Comprobando si un objeto es un número en C #

91

Me gustaría comprobar si un objeto es un número para que .ToString()daría lugar a una cadena que contiene dígitos y +, -,.

¿Es posible mediante una simple verificación de tipos en .net (como :) if (p is Number)?

¿O debería convertir a una cadena y luego intentar analizar para duplicar?

Actualización: para aclarar que mi objeto es int, uint, float, double, etc., no es una cadena. Estoy tratando de hacer una función que serialice cualquier objeto a xml como este:

<string>content</string>

o

<numeric>123.3</numeric>

o plantear una excepción.

Piotr Czapla
fuente
5
Parece que está intentando escribir su propio XmlSerializer. ¿Qué problema tiene el proveedor de .NET- msdn.microsoft.com/en-us/library/… ?
RichardOD
2
Es posible que pueda solucionar todo este problema definiendo su formato XML utilizando un XSD y luego creando un objeto en el que pueda serializar sus datos utilizando la herramienta XSD incluida: msdn.microsoft.com/en-us/library/x6c1kb0s % 28VS.71% 29.aspx
Dexter
@RichardOD: ¿Puedo usar la serialización xml para serializar el objeto []? Lo necesito para llamar a la función Flash adobe.com/livedocs/flex/201/html/wwhelp/wwhimpl/common/html/…
Piotr Czapla

Respuestas:

183

Simplemente necesitará hacer una verificación de tipo para cada uno de los tipos numéricos básicos.

Aquí hay un método de extensión que debería hacer el trabajo:

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

Esto debería cubrir todos los tipos numéricos.

Actualizar

Parece que realmente desea analizar el número de una cadena durante la deserialización. En este caso, probablemente sería mejor usarlo double.TryParse.

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

Por supuesto, esto no manejaría números enteros / decimales largos muy grandes, pero si ese es el caso, solo necesita agregar llamadas adicionales a long.TryParse/ decimal.TryParse/ cualquier otra cosa.

Noldorin
fuente
Mi objeto es int, short, uint, float, double o cualquier otra cosa que sea un número
Piotr Czapla
@Piotr: Ah, cierto. Parece que te entendí mal. Vea mi respuesta actualizada.
Noldorin
1
@Noldorin: de hecho, su versión anterior del código también funcionaría; simplemente agregue una verificación nula y use value.ToString (). Entonces no es necesario verificar todos los tipos numéricos.
Fredrik Mörk
1
@Noldorin Es triste que la solución correcta sea tan detallada :(.
Piotr Czapla
1
@Joe: En realidad, no haría ninguna diferencia, ya que ToString también usaría la cultura actual.
Noldorin
36

Tomado del Blog de 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);
}
Saul Dolgin
fuente
6
El problema con este enfoque es que si pasa una cadena que parece un número, la formateará. Puede que esté bien para la mayoría de la gente, pero fue un obstáculo para mí.
Rob Sedgwick
1
Otro problema potencial con esto es que no puede analizar los valores mínimo / máximo para el doble. double.Parse(double.MaxValue.ToString())causa un OverflowException. Puede remediar esto proporcionando el modificador de ida y vuelta .ToString("R")en este caso, pero esa sobrecarga no está disponible Convert.ToString(...)ya que no conocemos el tipo. Sé que este es un caso marginal, pero me encontré con él mientras escribía pruebas para mi propia .IsNumeric()extensión. Mi "solución" fue agregar un swtich de verificación de tipos antes de intentar analizar cualquier cosa, vea mi respuesta a esta pregunta para el código.
Ben
21

Aproveche la propiedad IsPrimitive para crear un método de extensión útil:

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

EDITAR: Corregido según los comentarios. Los genéricos se eliminaron desde los tipos de valor de las cajas .GetType (). También se incluye una corrección para los valores que aceptan valores NULL.

Kenan EK
fuente
1
La parte de los genéricos no le da ningún extra aquí, ¿verdad? solo accede a GetType () que está disponible en el objeto ...
Peter Lillevold
Guarda una operación de caja si se llama en un tipo de valor. Piense en la reutilización.
Kenan EK
1
¿Por qué no usar typeof (T) en lugar de obj.GetType, de esa manera no obtendrá una NullReferenceException si alguien pasa un tipo de referencia nulo? También puede poner una restricción genérica en T para aceptar solo tipos de valor. Por supuesto, si lo hace, comienza a tener mucha información en tiempo de compilación.
Trillian
objecty stringno son tipos primitivos.
We Are All Monica
@jnylen: esta respuesta fue hace bastante tiempo. Creo que desenterré algo de la fuente del marco reflectorizado en ese momento, pero quién puede decirlo hoy ... Respuesta fija.
Kenan EK
10

Hay algunas buenas respuestas arriba. Aquí tienes una solución todo en uno. Tres sobrecargas para diferentes circunstancias.

// 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)); }
Mick Bruno
fuente
considere agregar cheque nulo
wiero
Realmente no necesita verificación nula; como método de extensión, no podría llamarlo con un valor nulo. Por supuesto, alguien aún podría llamar como una función normal, pero ese no es el uso esperado de un método de extensión.
Mick Bruno
5
Creo que uno puede llamarlo con valor nulo. objeto obj = nulo; obj.IsNumeric ();
wiero
Gracias Weiro, lo he arreglado. No me di cuenta de que era posible llamar al método de extensión con un valor nulo, ¡pero por supuesto que lo es!
Mick Bruno
Creo que a la primera sobrecarga le falta un paréntesis al final: "return (x == null? False: IsNumeric (x.GetType ()));"
glenn garson
8

En lugar de lanzar el suyo, la forma más confiable de saber si un tipo incorporado es numérico es probablemente hacer referencia Microsoft.VisualBasicy llamar Information.IsNumeric(object value). La implementación maneja una serie de casos sutiles como char[]cadenas HEX y OCT.

satnhak
fuente
¡Esto debería estar en la parte superior!
Nawfal
4

Allí hay tres conceptos diferentes:

  • para comprobar si es un número (es decir, un valor numérico (normalmente en un cuadro)), marque el tipo con is- por ejemploif(obj is int) {...}
  • para comprobar si una cadena se puede analizar como un número; utilizarTryParse()
  • pero si el objeto no es un número o una cadena, pero sospecha que ToString()puede dar algo que parece un número, entonces llámelo ToString() y trátelo como una cadena

En los dos primeros casos, probablemente tendrá que manejar por separado cada tipo numérico que desee admitir ( double/ decimal/ int); cada uno tiene diferentes rangos y precisión, por ejemplo.

También puede mirar las expresiones regulares para una verificación rápida y preliminar.

Marc Gravell
fuente
4

Suponiendo que su entrada es una cadena ...

Hay 2 formas:

usar Double.TryParse ()

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

usar Regex

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

Podrías usar un código como este:

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

Si el objeto es una Int32, Single, Doubleetc. Se llevará a cabo la conversión. Además, se implementa una cadena, IConvertiblepero si la cadena no se puede convertir en un doble, se FormatExceptionlanzará a.

Martin Liversage
fuente
En realidad, las cadenas se analizarán, pero si no están en el formato correcto, se lanza FormatException.
alfoks
@alfoks: Tiene toda la razón, así que he actualizado la respuesta.
Martin Liversage
1

Si tu requerimiento es realmente

.ToString () daría como resultado una cadena que contiene dígitos y +, - ,.

y desea usar double.TryParse, entonces debe usar la sobrecarga que toma un parámetro NumberStyles y asegurarse de que está usando la cultura invariante.

Por ejemplo, para un número que puede tener un signo inicial, sin espacios en blanco iniciales o finales, sin separador de miles y un separador decimal de punto, utilice:

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

Mientras escribía mi propio object.IsNumeric()método de extensión basado en la respuesta de Saul Dolgin a esta pregunta, me encontré con un problema potencial en el que obtendrá un OverflowExceptionsi lo intenta con double.MaxValueo double.MinValue.

Mi "solución" fue combinar la respuesta aceptada de Noldorin con la de Saul Dolgin y agregar un interruptor de coincidencia de patrones antes de intentar analizar cualquier cosa (y usar algo de bondad de C # 7 para ordenar un poco):

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 _);
}
Ben
fuente
Tenga cuidado con los tipos que aceptan valores NULL.
Guillermo Prandi
0

Si, esto funciona:

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

Para un número de punto flotante, tendría que probar usando el tipo flotante:

object x = 1f;
Assert.That(x is float);
Peter Lillevold
fuente
Esto funcionará si el objeto era un int antes de convertirse implícita o explícitamente en un objeto. En su ejemplo, el número mágico 1 es un int, y luego se convierte implícitamente en el tipo de la variable x. Si hubiera hecho el objeto x = 1.0, su aserción habría devuelto falso.
Dexter
Hay números que no son enteros.
Fredrik Mörk
sí, entonces mi punto es básicamente lo que @Noldorin tiene en su respuesta en este momento.
Peter Lillevold