C # - como determinar se um tipo é um número


105

Existe uma maneira de determinar se um determinado tipo .Net é ou não um número? Por exemplo: System.UInt32/UInt16/Doublesão todos números. Quero evitar um longo caso de interruptor no Type.FullName.


4
Engano de muitos, muitos, muitos. Por que ainda não foi fechado?
Noldorin

2
Duplicado de stackoverflow.com/questions/1130698 e muito próximo de alguns outros.
Henk Holterman

Respostas:


110

Experimente isto:

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

Os tipos primitivos são Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, Char, Double e Single.

Levando a solução de Guillaume um pouco mais longe:

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
Então o decimaltipo não é numérico?
LukeH

2
@Xaero: Não tenho dúvidas de que decimal é numérico. Só porque não é um primitivo, não significa que não seja numérico. Seu código precisa levar em conta isso.
LukeH

2
Isso precisaria ser reprojetado para os novos tipos numéricos no .NET 4.0 que não têm códigos de tipo.
Jon Skeet

7
Como você pode me negar em uma resposta com base na tecnologia atual. Talvez no .NET 62, int seja removido - você vai fazer downvote em todas as respostas com int?
Philip Wallace

1
@DiskJunky Desculpe, amigo. Isso foi há quase três anos e não me lembro qual era o conteúdo.
kdbanman

93

Não use uma chave - apenas use um conjunto:

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

EDIT: Uma vantagem disso sobre o uso de um código de tipo é que quando novos tipos numéricos são introduzidos no .NET (por exemplo, BigInteger e Complex ) é fácil de ajustar - enquanto esses tipos não recebem um código de tipo.


4
e como você usaria o HashSet?
RvdK

8
NumericTypes.Contains (qualquer)?
mqp

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

Teria pensado que o compilador faria uma conversão implícita da instrução switch em hashset.
Rolf Kristensen

6
@RolfKristensen: Bem, switchsimplesmente não funciona Type, então você não pode. Você pode ligar TypeCode, é claro, mas isso é diferente.
Jon Skeet

69

Nenhuma das soluções leva Nullable em consideração.

Modifiquei um pouco a solução 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));
    }

Eu sei que poderia apenas adicionar os próprios nulos ao meu HashSet. Mas esta solução evita o perigo de esquecer de adicionar um Nullable específico à sua lista.

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

2
Um tipo anulável é realmente numérico? Nulo não é um número, que eu saiba.
IllidanS4 quer Monica de volta

2
Isso depende do que você deseja alcançar. No meu caso, também precisei incluir anuláveis. Mas também posso pensar em situações em que esse não seja um comportamento desejado.
Jürgen Steinblock

Boa! Tratar o número anulável como número é muito útil na validação de entrada da IU.
guogangj

1
@ IllidanS4 A verificação está no tipo, não no valor. Na maioria dos casos, os tipos numéricos anuláveis ​​devem ser tratados como numéricos. Obviamente, se a verificação foi feita em value e value for null, então sim, ele não deve ser considerado 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 a otimização removida (ver comentários da enzi) E se você realmente deseja otimizá-la (perdendo legibilidade e alguma segurança ...):

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
Sei que essa resposta é antiga, mas recentemente me deparei com uma opção dessas: não use a otimização sugerida! Eu olhei para o código IL gerado a partir de tal switch e notei que o compilador já aplica a otimização (em IL 5 é subtraído do código de tipo e então os valores de 0 a 10 são considerados verdadeiros). Portanto, o switch deve ser usado porque é mais legível, seguro e rápido.
enzi

1
Se você realmente deseja otimizá-lo e não se importa com a legibilidade, o código ideal seria return unchecked((uint)Type.GetTypeCode(type) - 5u) <= 10u;remover o desvio introduzido por &&.
AnorZaken,

14

Basicamente a solução de Skeet, mas você pode reutilizá-la com tipos anuláveis ​​da seguinte forma:

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

Abordagem baseada na proposta de Philip , aprimorada com a verificação de tipo interna do 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;
        }
    }
}

Porque isso? Tive que verificar se um dado Type typeé um tipo numérico, e não se um arbitrário object oé numérico.


4

Com o C # 7, esse método me dá melhor desempenho do que ligar a caixa TypeCodee 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;

Os testes são os seguintes:

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

Você poderia usar Type.IsPrimitive e, em seguida, classificar os tipos Booleane Char, algo assim:

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

EDITAR : Você pode excluir os tipos IntPtre UIntPtrtambém, se não os considerar numéricos.


1
Então o decimaltipo não é numérico?
LukeH

Opa ... bem, parece que a solução de Guillaume é a melhor, afinal.
Konamiman

3

Extensão de tipo com suporte 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

Resposta curta: Não.

Resposta mais longa: Não.

O fato é que muitos tipos diferentes em C # podem conter dados numéricos. A menos que você saiba o que esperar (Int, Double, etc), você precisa usar a instrução case "longa".


1

Isso também pode funcionar. No entanto, você pode querer seguir com um Type.Parse para lançá-lo da maneira que você deseja depois.

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

1

Skeet modificados e solução de arviman utilizando Generics, Reflectione 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 retorna falso.


1

O switch é um pouco lento, porque toda vez que os métodos na pior situação passarão por todos os tipos. Eu acho que usar o Dictonário é mais legal, nessa situação você terá 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

Experimente o pacote nuget TypeSupport para C #. Possui suporte para detecção de todos os tipos numéricos (entre muitos outros recursos):

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

Eu não conhecia este pacote. Parece ser um salvador em muitos casos, evitar escrever nosso próprio código para o tipo de operações solicitadas pelo OP. Obrigado !
AFract

0

Infelizmente, esses tipos não têm muito em comum além de serem todos tipos de valor. Mas para evitar um longo caso de alternância, você poderia apenas definir uma lista somente leitura com todos esses tipos e, em seguida, apenas verificar se o tipo fornecido está dentro da lista.


0

Eles são todos tipos de valor (exceto para bool e talvez enum). Então, você pode simplesmente usar:

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

1
Isso retornará verdadeiro para qualquer definido pelo usuário struct... Não acho que é isso que você deseja.
Dan Tao

1
Você está certo. Os tipos numéricos integrados também são estruturas. Então é melhor ir com a comparação primitiva.
MandoMando

0

EDIT: Bem, eu modifiquei o código abaixo para ter mais desempenho e, em seguida, executei os testes postados por @Hugo contra ele. As velocidades estão quase no mesmo nível do IF de @Hugo usando o último item de sua sequência (decimal). No entanto, se usar o primeiro item 'byte', ele leva o bolo, mas claramente a ordem é importante quando se trata de desempenho. Embora usar o código abaixo seja mais fácil de escrever e mais consistente em seu custo, ele não pode ser mantido ou preparado para o futuro.

Parece que mudar de Type.GetTypeCode () para Convert.GetTypeCode () acelerou o desempenho drasticamente, cerca de 25%, VS Enum.Parse () que era cerca de 10 vezes mais lento.


Eu sei que esta postagem é antiga, mas SE você estiver usando o método enum TypeCode, o mais fácil (e provavelmente o mais barato) seria algo assim:

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

Dada a seguinte definição de enum 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
}

Eu não testei completamente, mas para tipos numéricos C # básicos, isso parece cobrir tudo. No entanto, como @JonSkeet mencionou, este enum não é atualizado para tipos adicionais adicionados ao .NET no futuro.


-1

oops! Interpretou mal a pergunta! Pessoalmente, iria rolar com Skeet's .


HRM, parece que você quer DoSomethingna Typede seus dados. O que você poderia fazer é o seguinte

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;
    }
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.