C #: cómo determinar si un tipo es un número


105

¿Hay alguna forma de determinar si un tipo de .Net dado es un número o no? Por ejemplo: System.UInt32/UInt16/Doubleson todos números. Quiero evitar una larga caja de interruptores en el Type.FullName.


4
Embaucado de muchos, muchos, muchos. ¿Por qué no se ha cerrado todavía?
Noldorin

2
Duplicado de stackoverflow.com/questions/1130698 y muy cercano a algunos otros.
Henk Holterman

Respuestas:


110

Prueba esto:

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

Los tipos primitivos son Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, Char, Double y Single.

Llevando la solución de Guillaume un poco más allá:

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

Uso:

int i = 32;
i.IsNumericType(); // True

string s = "Hello World";
s.IsNumericType(); // False

2
¿Entonces el decimaltipo no es numérico?
LukeH

2
@Xaero: No tengo ninguna duda de que decimal es numérico. El hecho de que no sea un primitivo no significa que no sea numérico. Su código debe tener en cuenta esto.
LukeH

2
Esto tendría que ser rediseñado para los nuevos tipos numéricos en .NET 4.0 que no tienen códigos de tipo.
Jon Skeet

7
¿Cómo puede rechazarme una respuesta basada en la tecnología actual? Tal vez en .NET 62, int se eliminará, ¿va a rechazar todas las respuestas con int?
Philip Wallace

1
@DiskJunky Lo siento, amigo. Eso fue hace casi tres años y no recuerdo cuál era su contenido.
kdbanman

93

No use un interruptor, solo use un conjunto:

HashSet<Type> NumericTypes = new HashSet<Type>
{
    typeof(decimal), typeof(byte), typeof(sbyte),
    typeof(short), typeof(ushort), ...
};

EDITAR: Una ventaja de esto sobre el uso de un código de tipo es que cuando se introducen nuevos tipos numéricos en .NET (por ejemplo, BigInteger y Complex ) es fácil de ajustar, mientras que esos tipos no obtendrán un código de tipo.


4
y ¿cómo usarías el HashSet?
RvdK

8
NumericTypes.Contains (lo que sea)?
mqp

3
bool isANumber = NumericTypes.Contains (classInstance.GetType ());
Yuriy Faktorovich

Habría pensado que el compilador haría una conversión implícita de la instrucción switch a hashset.
Rolf Kristensen

6
@RolfKristensen: Bueno, switchsimplemente no funciona Type, así que no puedes. Puede encender, por TypeCodesupuesto, pero eso es un asunto diferente.
Jon Skeet

69

Ninguna de las soluciones tiene en cuenta Nullable.

Modifiqué un poco la solución de Jon Skeet:

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

Sé que podría agregar los nullables a mi HashSet. Pero esta solución evita el peligro de olvidar agregar un Nullable específico a su lista.

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

2
¿Es un tipo que acepta valores NULL realmente numérico? Nulo no es un número, que yo sepa.
IllidanS4 quiere que Monica vuelva

2
Eso depende de lo que quieras lograr. En mi caso, también necesitaba incluir nullables. Pero también podría pensar en situaciones en las que este no es un comportamiento deseado.
Jürgen Steinblock

¡Bueno! Tratar el número que acepta valores NULL como un número es muy útil en la validación de entrada de la interfaz de usuario.
guogangj

1
@ IllidanS4 La verificación está en Escriba, no en el valor. En la mayoría de los casos, los tipos numéricos que aceptan valores NULL deben tratarse como numéricos. Por supuesto, si la verificación estaba en el valor y el valor es nulo, entonces sí, no debería considerarse numérico.
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;
  }
}

Nota sobre la optimización eliminada (ver comentarios de enzi) Y si realmente quieres optimizarla (perdiendo legibilidad y algo de seguridad ...):

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
Sé que esta respuesta es antigua, pero recientemente me encontré con un cambio de este tipo: ¡no use la optimización sugerida! Miré el código IL generado a partir de dicho conmutador y noté que el compilador ya aplica la optimización (en IL 5 se resta del código de tipo y luego los valores de 0 a 10 se consideran verdaderos). Por lo tanto, el interruptor debe usarse para que sea más legible, más seguro e igual de rápido.
enzi

1
Si realmente desea optimizarlo y no le importa la legibilidad, el código óptimo sería return unchecked((uint)Type.GetTypeCode(type) - 5u) <= 10u;eliminar la rama introducida por &&.
AnorZaken

14

Básicamente, la solución de Skeet, pero puede reutilizarla con tipos que aceptan valores NULL de la siguiente manera:

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

Enfoque basado en la propuesta de Philip , mejorado con la verificación de tipo interna de SFun28 para Nullabletipos:

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

¿Por qué esto? Tuve que verificar si un dado Type typees un tipo numérico y no si un arbitrario object oes numérico.


4

Con C # 7, este método me brinda un mejor rendimiento que encender la caja TypeCodey 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;

Las pruebas son las siguientes:

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

Puede usar Type.IsPrimitive y luego ordenar los tipos Booleany Char, algo como esto:

bool IsNumeric(Type type)
{
    return type.IsPrimitive && type!=typeof(char) && type!=typeof(bool);
}

EDITAR : Es posible que desee excluir a los IntPtry UIntPtrtipos, así, si usted no se considera que sean numérico.


1
¿Entonces el decimaltipo no es numérico?
LukeH

Vaya ... bueno, parece que la solución de Guillaume es la mejor después de todo.
Konamiman

3

Extensión de tipo con soporte de tipo nulo.

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

Respuesta corta: No.

Respuesta más larga: no.

El hecho es que muchos tipos diferentes en C # pueden contener datos numéricos. A menos que sepa qué esperar (Int, Double, etc.), debe utilizar la declaración de caso "larga".


1

Esto también puede funcionar. Sin embargo, es posible que desee seguirlo con un Type.Parse para lanzarlo de la manera que desee después.

public bool IsNumeric(object value)
{
    float testValue;
    return float.TryParse(value.ToString(), out testValue);
}

1

De skeet modificados y solución de arviman utilización Generics, Reflectiony 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)
};

Seguido por:

public static bool IsNumeric<T>( this T myType )
{
    var IsNumeric = false;

    if( myType != null )
    {
        IsNumeric = m_numTypes.Contains( myType.GetType() );
    }

    return IsNumeric;
}

Uso para (T item):

if ( item.IsNumeric() ) {}

null devuelve falso.


1

El cambio es un poco lento, porque cada vez que los métodos en la peor situación pasarán por todos los tipos. Creo que usar Dictonary es más agradable, en esta situación tendrás 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

Pruebe el paquete nuget TypeSupport para C #. Tiene soporte para detectar todos los tipos numéricos (entre muchas otras características):

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

No conocía este paquete. Parece ser un salvavidas en muchos casos evitar escribir nuestro propio código para el tipo de operaciones solicitadas por el OP. Gracias !
AFract

0

Desafortunadamente, estos tipos no tienen mucho en común aparte de que son todos tipos de valor. Pero para evitar un caso de cambio largo, puede definir una lista de solo lectura con todos estos tipos y luego verificar si el tipo dado está dentro de la lista.


0

Todos son tipos de valor (excepto bool y tal vez enum). Entonces, simplemente podría usar:

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

1
Esto volverá a ser verdadero para cualquier usuario definido struct... No creo que eso sea lo que quieres.
Dan Tao

1
Estás en lo correcto. Los tipos numéricos incorporados también son estructuras. Entonces, mejor vaya con la comparación primitiva.
MandoMando

0

EDITAR: Bueno, modifiqué el código a continuación para que sea más eficiente y luego ejecuté las pruebas publicadas por @Hugo en su contra. Las velocidades están aproximadamente a la par con el IF de @ Hugo usando el último elemento de su secuencia (decimal). Sin embargo, si usa el primer elemento 'byte', se lleva la palma, pero claramente el orden importa cuando se trata de rendimiento. Aunque usar el código a continuación es más fácil de escribir y más consistente en su costo, sin embargo, no se puede mantener ni se puede probar en el futuro.

Parece que cambiar de Type.GetTypeCode () a Convert.GetTypeCode () aceleró el rendimiento drásticamente, aproximadamente un 25%, VS Enum.Parse () que era como 10 veces más lento.


Sé que esta publicación es antigua, pero SI usa el método de enumeración TypeCode, lo más fácil (y probablemente el más barato) sería algo como esto:

public static bool IsNumericType(this object o)
{   
  var t = (byte)Convert.GetTypeCode(o);
  return t > 4 && t < 16;
}

Dada la siguiente definición de enumeración para 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
}

No lo he probado a fondo, pero para los tipos numéricos básicos de C #, esto parece cubrirlo. Sin embargo, como mencionó @JonSkeet, esta enumeración no se actualiza para tipos adicionales agregados a .NET en el futuro.


-1

¡Ups! ¡Leyó mal la pregunta! Personalmente, rodaría con Skeet's .


gestión de recursos humanos, como los sonidos que desea DoSomethingen Typesus datos. Lo que podrías hacer es lo siguiente

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;
    }
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.