Vérifier si un objet est un nombre en C #

89

Je voudrais vérifier si un objet est un numéro afin que .ToString()se traduirait par une chaîne contenant des chiffres et +, -,.

Est-ce possible par simple enregistrement de type .net (comme:) if (p is Number)?

Ou dois-je convertir en chaîne, puis essayer d'analyser pour doubler?

Mise à jour: pour clarifier mon objet est int, uint, float, double, etc., ce n'est pas une chaîne. J'essaie de créer une fonction qui sérialiserait n'importe quel objet en xml comme ceci:

<string>content</string>

ou

<numeric>123.3</numeric>

ou lever une exception.

Piotr Czapla
la source
5
On dirait que vous essayez d'écrire votre propre XmlSerializer - quel est le problème avec le fournisseur unique de .NET- msdn.microsoft.com/en-us/library / ... ?
RichardOD
2
Vous pourrez peut-être contourner tout ce problème en définissant votre format XML à l'aide d'un XSD, puis en créant un objet dans lequel vous pouvez sérialiser vos données à l'aide de l'outil XSD fourni - msdn.microsoft.com/en-us/library/x6c1kb0s % 28VS.71% 29.aspx
Dexter
@RichardOD: Puis-je utiliser la sérialisation xml pour sérialiser l'objet []? J'en ai besoin pour appeler la fonction Flash adobe.com/livedocs/flex/201/html/wwhelp/wwhimpl/common/html/…
Piotr Czapla

Réponses:

180

Vous aurez simplement besoin de faire une vérification de type pour chacun des types numériques de base.

Voici une méthode d'extension qui devrait faire le travail:

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

Cela devrait couvrir tous les types numériques.

Mettre à jour

Il semble que vous souhaitiez réellement analyser le nombre d'une chaîne lors de la désérialisation. Dans ce cas, il serait probablement préférable de l'utiliser double.TryParse.

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

Bien sûr, cela ne gérerait pas de très grands entiers / décimales longues, mais si tel est le cas, vous devez simplement ajouter des appels supplémentaires à long.TryParse/ decimal.TryParse/ quoi que ce soit d'autre.

Noldorin
la source
Mon objet est int, short, uint, float, double ou tout autre élément qui est un nombre
Piotr Czapla
@Piotr: Ah c'est vrai. Il semble que je vous ai mal compris. Voir ma réponse mise à jour.
Noldorin
1
@Noldorin: en fait, votre version précédente du code fonctionnerait également; ajoutez simplement une vérification nulle et utilisez value.ToString (). Ensuite, vous n'avez pas besoin de vérifier tous les types numériques.
Fredrik Mörk
1
@Noldorin Il est triste que la bonne solution soit aussi verbeuse :(.
Piotr Czapla
1
@Joe: En fait, cela ne ferait aucune différence, puisque ToString utiliserait également la culture actuelle.
Noldorin
36

Tiré du 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
la source
6
Le problème avec cette approche est que si vous passez une chaîne qui ressemble à un nombre, elle la formatera. Ça pourrait convenir à la plupart des gens, mais c'était un coup d'arrêt pour moi.
Rob Sedgwick
1
Un autre problème potentiel est que vous ne pouvez pas analyser les valeurs min / max pour double. double.Parse(double.MaxValue.ToString())provoque un OverflowException. Vous pouvez remédier à cela en fournissant le modificateur aller-retour .ToString("R")dans ce cas, mais cette surcharge n'est pas disponible Convert.ToString(...)car nous ne connaissons pas le type. Je sais que c'est un peu un cas marginal, mais je suis tombé dessus en écrivant des tests pour ma propre .IsNumeric()extension. Ma "solution" était d'ajouter un commutateur de vérification de type avant d'essayer d'analyser quoi que ce soit, voir ma réponse à cette question pour le code.
Ben
21

Tirez parti de la propriété IsPrimitive pour créer une méthode d'extension pratique:

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: Corrigé selon les commentaires. Les génériques ont été supprimés depuis les types de valeur des boîtes .GetType (). Correctif également inclus pour les valeurs Nullable.

Kenan EK
la source
1
La partie génériques ne vous donne aucun supplément ici, n'est-ce pas? vous n'accédez qu'à GetType () qui est disponible sur l'objet ...
Peter Lillevold
Il enregistre une opération de boîte s'il est appelé sur un type valeur. Pensez à la réutilisabilité.
Kenan EK
1
Pourquoi ne pas utiliser typeof (T) au lieu de obj.GetType, de cette façon, vous n'obtiendrez pas une NullReferenceException si quelqu'un passe un type de référence nul. Vous pouvez également mettre une contrainte générique sur T pour n'accepter que les types valeur. Bien sûr, vous commencez à avoir beaucoup d'informations au moment de la compilation si vous faites cela.
Trillian
objectet stringne sont pas des types primitifs.
We Are All Monica
@jnylen: cette réponse remonte à un certain temps. Je crois que j'ai déterré quelque chose de la source du cadre réflectorisé à l'époque, mais qui peut le dire aujourd'hui ... Réponse fixe.
Kenan EK
10

Il y a quelques bonnes réponses ci-dessus. Voici une solution tout-en-un. Trois surcharges pour différentes circonstances.

// 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
la source
envisager d'ajouter un chèque nul
wiero
Pas vraiment besoin de vérification null - en tant que méthode d'extension, vous ne pouvez pas l'appeler avec une valeur nulle. Bien sûr, quelqu'un peut toujours appeler comme une fonction normale, mais ce n'est pas l'usage attendu d'une méthode d'extension.
Mick Bruno
5
Je pense qu'on peut l'appeler avec une valeur nulle. objet obj = null; obj.IsNumeric ();
wiero
Merci Weiro, je l'ai réparé. Je n'ai pas réalisé que l'appel de la méthode d'extension avec une valeur nulle était possible, mais bien sûr!
Mick Bruno
Je pense que la première surcharge manque une parenthèse à la fin: "return (x == null? False: IsNumeric (x.GetType ()));"
glenn garson
8

Plutôt que de lancer le vôtre, le moyen le plus fiable de savoir si un type intégré est numérique est probablement de le référencer Microsoft.VisualBasicet de l'appeler Information.IsNumeric(object value). L'implémentation gère un certain nombre de cas subtils tels que les char[]chaînes HEX et OCT.

satnhak
la source
Cela devrait être au top!
nawfal le
4

Il y a trois concepts différents ici:

  • pour vérifier s'il s'agit d' un nombre (c'est-à-dire une valeur numérique (généralement encadrée) elle-même), vérifiez le type avecis - par exempleif(obj is int) {...}
  • pour vérifier si une chaîne peut être analysée comme un nombre; utilisationTryParse()
  • mais si l'objet n'est pas un nombre ou une chaîne, mais que vous pensez qu'il ToString()pourrait donner quelque chose qui ressemble à un nombre, appelez-le ToString() et traitez-le comme une chaîne

Dans les deux premiers cas, vous devrez probablement gérer séparément chaque type numérique que vous souhaitez prendre en charge ( double/ decimal/ int) - chacun a des plages et une précision différentes, par exemple.

Vous pouvez également consulter regex pour une vérification rapide et approximative.

Marc Gravell
la source
4

En supposant que votre entrée est une chaîne ...

Il existe 2 façons:

utilisez Double.TryParse ()

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

utiliser Regex

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

Vous pouvez utiliser un code comme celui-ci:

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

Si votre objet est un Int32, Single, Doubleetc. , il effectuera la conversion. De plus, une chaîne implémente IConvertiblemais si la chaîne n'est pas convertible en double, alors un FormatExceptionsera lancé.

Martin Liversage
la source
En fait, les chaînes seront analysées, mais si elles ne sont pas au format correct, FormatException est levée.
alfoks
@alfoks: Vous avez absolument raison, j'ai donc mis à jour la réponse.
Martin Liversage
1

Si votre exigence est vraiment

.ToString () donnerait une chaîne contenant des chiffres et +, - ,.

et que vous souhaitez utiliser double.TryParse, vous devez utiliser la surcharge qui prend un paramètre NumberStyles et vous assurer que vous utilisez la culture invariante.

Par exemple, pour un nombre qui peut avoir un signe de début, aucun espace blanc de début ou de fin, aucun séparateur de milliers et un séparateur décimal de période, utilisez:

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

En écrivant ma propre object.IsNumeric()méthode d'extension basée sur la réponse de Saul Dolgin à cette question, j'ai rencontré un problème potentiel en ce que vous obtiendrez un OverflowExceptionsi vous l'essayez avec double.MaxValueoudouble.MinValue .

Ma «solution» a été de combiner la réponse acceptée de Noldorin avec celle de Saul Dolgin et d'ajouter un commutateur de correspondance de modèle avant d'essayer d'analyser quoi que ce soit (et d'utiliser un peu de bonté C # 7 pour ranger un peu):

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
la source
0

Oui, cela fonctionne:

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

Pour un nombre à virgule flottante, vous devrez tester en utilisant le type float:

object x = 1f;
Assert.That(x is float);
Peter Lillevold
la source
Cela fonctionnera si l'objet était un int avant d'être converti implicitement ou explicitement en objet. Dans votre exemple, le nombre magique 1 est un int, et est alors implicitement converti dans le type de la variable x .. Si vous aviez fait object x = 1.0, votre assertion aurait retourné false.
Dexter
Il y a des nombres qui ne sont pas des entiers.
Fredrik Mörk
oui, donc mon point est essentiellement ce que @Noldorin a dans sa réponse en ce moment.
Peter Lillevold