Verificar se um objeto é um número em C #

89

Eu gostaria de verificar se um objeto é um número para que .ToString()resultaria em uma string contendo dígitos e +, -,.

É possível digitando simplesmente em .net (como:) if (p is Number)?

Ou devo converter em string e depois tentar analisar em double?

Atualização: Para esclarecer, meu objeto é int, uint, float, double e assim por diante, não é uma string. Estou tentando fazer uma função que serializaria qualquer objeto em xml como este:

<string>content</string>

ou

<numeric>123.3</numeric>

ou gerar uma exceção.

Piotr Czapla
fonte
5
Parece que você está tentando escrever seu próprio XmlSerializer- o que há de errado com o provedor .NET- msdn.microsoft.com/en-us/library/… ?
RichardOD
2
Você pode ser capaz de contornar todo esse problema definindo seu formato XML usando um XSD e, em seguida, criando um objeto no qual você pode serializar seus dados usando a ferramenta XSD enviada - msdn.microsoft.com/en-us/library/x6c1kb0s % 28VS.71% 29.aspx
Dexter
@RichardOD: Posso usar a serialização xml para serializar o objeto []? Eu preciso chamar a função Flash adobe.com/livedocs/flex/201/html/wwhelp/wwhimpl/common/html/…
Piotr Czapla

Respostas:

180

Você simplesmente precisará fazer uma verificação de tipo para cada um dos tipos numéricos básicos.

Aqui está um método de extensão que deve fazer o trabalho:

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

Isso deve abranger todos os tipos numéricos.

Atualizar

Parece que você realmente deseja analisar o número de uma string durante a desserialização. Nesse caso, provavelmente seria melhor usar double.TryParse.

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

Claro, isso não lidaria com inteiros muito grandes / decimais longos, mas se for esse o caso, você só precisa adicionar chamadas para long.TryParse/ decimal.TryParse/ qualquer outra coisa.

Noldorin
fonte
Meu objeto é int, short, uint, float, double ou qualquer outra coisa que seja um número
Piotr Czapla
@Piotr: Ah, certo. Parece que te entendi mal. Veja minha resposta atualizada.
Noldorin
1
@Noldorin: na verdade, sua versão anterior do código também funcionaria; basta adicionar uma verificação de nulo e usar value.ToString (). Então você não precisa verificar todos os tipos numéricos.
Fredrik Mörk
1
@Noldorin É triste que a solução certa seja tão prolixo :(.
Piotr Czapla
1
@Joe: Na verdade, não faria diferença, já que ToString também usaria a cultura atual.
Noldorin
36

Retirado do 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
fonte
6
O problema com essa abordagem é se você passar uma string que se pareça com um número, ela será formatada. Pode ser bom para a maioria das pessoas, mas foi um obstáculo para mim.
Rob Sedgwick de
1
Outro problema potencial com isso é que você não pode analisar os valores mínimo / máximo para double. double.Parse(double.MaxValue.ToString())causa um OverflowException. Você poderia remediar isso fornecendo o modificador de ida e volta .ToString("R")neste caso, mas essa sobrecarga não está disponível porque Convert.ToString(...)não sabemos o tipo. Eu sei que este é um caso um pouco marginal, mas tropecei nele enquanto escrevia testes para minha própria .IsNumeric()extensão. Minha "solução" foi adicionar um swtich de verificação de tipo antes de tentar analisar qualquer coisa, veja minha resposta a esta pergunta para o código.
Ben
21

Aproveite a propriedade IsPrimitive para fazer um método de extensão ú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);
}

EDIT: Corrigido de acordo com comentários. Os genéricos foram removidos desde as caixas .GetType () tipos de valor. Também inclui correção para valores anuláveis.

Kenan EK
fonte
1
A parte dos genéricos não está dando nenhum extra aqui, não é? você só acessa GetType () que está disponível no objeto ...
Peter Lillevold
Ele salva uma operação de caixa se for chamado em um tipo de valor. Pense em reutilização.
Kenan EK
1
Por que não usar typeof (T) em vez de obj.GetType, dessa forma você não obterá um NullReferenceException se alguém passar um tipo de referência nulo. Você também pode colocar uma restrição genérica em T para aceitar apenas tipos de valor. É claro que você começa a ter muitas informações em tempo de compilação se fizer isso.
Trillian de
objecte stringnão são tipos primitivos.
Somos Todos Monica
@jnylen: essa resposta foi há muito tempo. Acho que descobri algo da fonte da estrutura reflectorizada na época, mas quem pode dizer hoje ... Resposta fixa.
Kenan EK de
10

Existem algumas ótimas respostas acima. Aqui está uma solução tudo-em-um. Três sobrecargas para diferentes circunstâncias.

// 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
fonte
considere adicionar verificação nula
wiero
Realmente não precisa de verificação de nulo - como um método de extensão, você não poderia chamá-lo com um valor nulo. Claro que alguém ainda pode chamar como uma função normal, mas esse não é o uso esperado de um método de extensão.
Mick Bruno
5
Acho que se pode chamá-lo com valor nulo. objeto obj = nulo; obj.IsNumeric ();
wiero
Obrigado Weiro, consertamos isso. Não sabia que era possível chamar o método de extensão com um valor nulo, mas é claro que é!
Mick Bruno
Acho que a primeira sobrecarga está faltando um parêntese no final: "return (x == null? False: IsNumeric (x.GetType ()));"
glenn garson
8

Em vez de lançar o seu próprio, a maneira mais confiável de saber se um tipo embutido é numérico é provavelmente referenciar Microsoft.VisualBasice chamar Information.IsNumeric(object value). A implementação lida com vários casos sutis, como char[]strings HEX e OCT.

satnhak
fonte
Isso deve estar no topo!
nawfal
4

Existem três conceitos diferentes lá:

  • para verificar se é um número (ou seja, um valor numérico (normalmente em caixa) em si), verifique o tipo com is- por exemploif(obj is int) {...}
  • para verificar se uma string pode ser analisada como um número; usarTryParse()
  • mas se o objeto não for um número ou uma string, mas você suspeitar que ToString()pode fornecer algo que se pareça com um número, ligue ToString() e trate-o como uma string

Nos dois primeiros casos, você provavelmente terá que lidar separadamente com cada tipo numérico que deseja oferecer suporte ( double/ decimal/ int) - cada um tem diferentes intervalos e precisão, por exemplo.

Você também pode consultar o regex para uma verificação geral rápida.

Marc Gravell
fonte
4

Supondo que sua entrada seja uma string ...

Existem 2 maneiras:

use Double.ExperimenteParse ()

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

usar Regex

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

Você pode usar um código como este:

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

Se o objeto é um Int32, Single, Doubleetc, irá efectuar a conversão. Além disso, uma string implementa, IConvertiblemas se a string não for conversível em um duplo, um FormatExceptionserá lançado.

Martin Liversage
fonte
Na verdade, as strings serão analisadas, mas se não estiverem no formato correto, FormatException é lançada.
alfoks de
@alfoks: Você está absolutamente certo, então atualizei a resposta.
Martin Liversage
1

Se sua exigência é realmente

.ToString () resultaria em uma string contendo dígitos e +, - ,.

e você deseja usar double.ExperimenteParse, então você precisa usar a sobrecarga que leva um parâmetro NumberStyles e certifique-se de que está usando a cultura invariável.

Por exemplo, para um número que pode ter um sinal à esquerda, nenhum espaço em branco à esquerda ou à direita, nenhum separador de milhar e um separador decimal de ponto, use:

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

Ao escrever meu próprio object.IsNumeric()método de extensão com base na resposta de Saul Dolgin a esta pergunta, encontrei um problema potencial em que você obterá um OverflowExceptionse tentar com double.MaxValueoudouble.MinValue .

Minha "solução" foi combinar a resposta aceita de noldorin com a de Saul Dolgin e adicionar uma chave de correspondência de padrões antes de tentar analisar qualquer coisa (e usar alguns recursos do C # 7 para arrumar um pouco):

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
fonte
0

Sim, isso funciona:

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

Para um número de ponto flutuante, você teria que testar usando o tipo flutuante:

object x = 1f;
Assert.That(x is float);
Peter Lillevold
fonte
Isso funcionará se o objeto era um int antes de ser implícita ou explicitamente convertido em um objeto. Em seu exemplo, o número mágico 1 é um int, e é então convertido implicitamente no tipo da variável x .. Se você tivesse feito o objeto x = 1,0, sua declaração teria retornado falso.
Dexter
Existem números que não são ints.
Fredrik Mörk
sim, então meu ponto é basicamente o que @Noldorin tem em sua resposta agora.
Peter Lillevold