C # - comment déterminer si un type est un nombre

105

Existe-t-il un moyen de déterminer si un type .Net donné est ou non un nombre? Par exemple: System.UInt32/UInt16/Doublesont tous des nombres. Je veux éviter un long boîtier de commutation sur le Type.FullName.

Adi Barda
la source
4
Dupe de beaucoup, beaucoup, beaucoup. Pourquoi cela n'a-t-il pas encore été fermé?
Noldorin
2
Duplicata de stackoverflow.com/questions/1130698 et très proche de quelques autres.
Henk Holterman

Réponses:

110

Essaye ça:

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

Les types primitifs sont Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, Char, Double et Single.

Poussant un peu plus loin la solution de Guillaume :

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

Usage:

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

string s = "Hello World";
s.IsNumericType(); // False
Philip Wallace
la source
2
Donc le decimaltype n'est pas numérique?
LukeH
2
@Xaero: Je ne doute pas que ce decimal soit numérique. Ce n'est pas parce que ce n'est pas une primitive que ce n'est pas numérique. Votre code doit en tenir compte.
LukeH
2
Cela devrait être repensé pour les nouveaux types numériques dans .NET 4.0 qui n'ont pas de codes de type.
Jon Skeet
7
Comment pouvez-vous me refuser une réponse basée sur la technologie actuelle. Peut-être que dans .NET 62, int sera supprimé - allez-vous voter contre toutes les réponses avec int?
Philip Wallace
1
@DiskJunky Désolé, mon ami. C'était il y a presque trois ans et je ne me souviens pas de leur contenu.
kdbanman
93

N'utilisez pas d'interrupteur - utilisez simplement un ensemble:

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

EDIT: Un avantage de ceci par rapport à l'utilisation d'un code de type est que lorsque de nouveaux types numériques sont introduits dans .NET (par exemple BigInteger et Complex ), il est facile à ajuster - alors que ces types n'obtiendront pas de code de type.

Jon Skeet
la source
4
et comment utiliseriez-vous le HashSet?
RvdK
8
NumericTypes.Contains (peu importe)?
mqp
3
booléen isANumber = NumericTypes.Contains (classInstance.GetType ());
Yuriy Faktorovich
J'aurais pensé que le compilateur ferait une conversion implicite de l'instruction switch en hashset.
Rolf Kristensen
6
@RolfKristensen: Eh bien, switchcela ne fonctionne tout simplement pas Type, donc vous ne pouvez pas. Vous pouvez TypeCodebien sûr allumer , mais c'est une autre question.
Jon Skeet
69

Aucune des solutions ne prend en compte Nullable.

J'ai un peu modifié la solution 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));
    }

Je sais que je pourrais simplement ajouter les nullables lui-même à mon HashSet. Mais cette solution évite le risque d'oublier d'ajouter un Nullable spécifique à votre liste.

    private static HashSet<Type> NumericTypes = new HashSet<Type>
    {
        typeof(int),
        typeof(int?),
        ...
    };
Jürgen Steinblock
la source
2
Un type Nullable est-il vraiment numérique? Null n'est pas un nombre, à ma connaissance.
IllidanS4 veut que Monica revienne
2
Cela dépend de ce que vous voulez réaliser. Dans mon cas, je devais également inclure les nullables. Mais je pourrais aussi penser à des situations où ce n'est pas un comportement souhaité.
Jürgen Steinblock
Bien! Traiter un nombre Nullable comme un nombre est très utile dans la validation d'entrée de l'interface utilisateur.
guogangj
1
@ IllidanS4 La vérification est sur Tapez pas la valeur. Dans la plupart des cas, les types numériques Nullable doivent être traités comme numériques. Bien sûr, si le contrôle était sur la valeur et que la valeur est nulle, alors oui, elle ne devrait pas être considérée comme numérique.
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;
  }
}

Remarque sur l'optimisation supprimée (voir les commentaires enzi) Et si vous voulez vraiment l'optimiser (perte de lisibilité et de sécurité ...):

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

Guillaume
la source
13
Je sais que cette réponse est ancienne, mais je suis récemment tombé sur un tel commutateur: n'utilisez pas l'optimisation suggérée! J'ai regardé le code IL généré à partir d'un tel commutateur et j'ai noté que le compilateur applique déjà l'optimisation (dans IL 5 est soustrait du code de type, puis les valeurs de 0 à 10 sont considérées comme vraies). Par conséquent, le commutateur doit être utilisé car il est plus lisible, plus sûr et tout aussi rapide.
enzi
1
Si vous voulez réellement l'optimiser et que vous ne vous souciez pas de la lisibilité, le code optimal serait return unchecked((uint)Type.GetTypeCode(type) - 5u) <= 10u;donc de supprimer la branche introduite par &&.
AnorZaken
14

Fondamentalement, la solution de Skeet, mais vous pouvez la réutiliser avec les types Nullable comme suit:

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);
    }
}
Arviman
la source
9

Approche basée sur la proposition de Philip , améliorée avec la vérification de type interne de SFun28 pour les Nullabletypes:

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

Pourquoi ça? J'ai dû vérifier si un donné Type typeest un type numérique, et non si un arbitraire object oest numérique.

cimnine
la source
4

Avec C # 7, cette méthode me donne de meilleures performances que d'activer le boîtier TypeCodeet 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;

Les tests sont les suivants:

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);
    }
Hugo Freitas
la source
3

Vous pouvez utiliser Type.IsPrimitive , puis trier les types Booleanet Char, quelque chose comme ceci:

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

EDIT : Vous pouvez exclure les IntPtret UIntPtrtypes aussi bien, si vous ne les considérez pas comme numérique.

Konamiman
la source
1
Donc le decimaltype n'est pas numérique?
LukeH
Oups ... eh bien, il semble que la solution de Guillaume soit la meilleure après tout.
Konamiman
3

Extension de type avec prise en charge de type nul.

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;
        }
    }
Mateusz Kraska
la source
1

Réponse courte: Non.

Réponse plus longue: Non.

Le fait est que de nombreux types différents en C # peuvent contenir des données numériques. À moins que vous ne sachiez à quoi vous attendre (Int, Double, etc.), vous devez utiliser l'instruction case "long".

Craig
la source
1

Cela peut également fonctionner. Cependant, vous souhaiterez peut-être le suivre avec un Type.Parse pour le convertir comme vous le souhaitez par la suite.

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

L' utilisation de modification au pigeon d' argile et la solution de arviman Generics, Reflectionet 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)
};

Suivi par:

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

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

    return IsNumeric;
}

Utilisation pour (T item):

if ( item.IsNumeric() ) {}

null renvoie false.

Un chat domestique
la source
1

Le changement est un peu lent, car chaque fois que les méthodes dans la pire des situations passeront par tous les types. Je pense que l'utilisation de Dictonary est plus agréable, dans cette situation, vous aurez 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);
    }
}
Smagin Alexey
la source
1

Essayez le package nuget TypeSupport pour C #. Il prend en charge la détection de tous les types numériques (parmi de nombreuses autres fonctionnalités):

var extendedType = typeof(int).GetExtendedType();
Assert.IsTrue(extendedType.IsNumericType);
Michael Brown
la source
Je ne connaissais pas ce paquet. Semble être un sauveur de vie dans de nombreux cas pour éviter d'écrire notre propre code pour le type d'opérations demandées par l'OP. Merci !
AFract le
0

Malheureusement, ces types n'ont pas grand-chose en commun à part ce sont tous des types de valeur. Mais pour éviter un long cas de commutation, vous pouvez simplement définir une liste en lecture seule avec tous ces types et ensuite simplement vérifier si le type donné est dans la liste.

Darin Dimitrov
la source
0

Ce sont tous des types de valeur (sauf pour bool et peut-être enum). Vous pouvez donc simplement utiliser:

bool IsNumberic(object o)
{
    return (o is System.ValueType && !(o is System.Boolean) && !(o is System.Enum))
}
MandoMando
la source
1
Cela retournera vrai pour tout utilisateur défini struct... Je ne pense pas que ce soit ce que vous voulez.
Dan Tao
1
Vous avez raison. Les types numériques intégrés sont également des structures. Alors mieux vaut aller avec la comparaison primitive alors.
MandoMando
0

EDIT: Eh bien, j'ai modifié le code ci-dessous pour être plus performant, puis j'ai exécuté les tests postés par @Hugo contre lui. Les vitesses sont à peu près équivalentes à celles de @ Hugo en utilisant le dernier élément de sa séquence (Decimal). Cependant, si vous utilisez le premier élément «octet», il prend le gâteau, mais l'ordre est clairement important en termes de performances. Bien que l'utilisation du code ci-dessous soit plus facile à écrire et plus cohérente en termes de coût, elle n'est cependant pas maintenable ou à l'épreuve du futur.

On dirait que le passage de Type.GetTypeCode () à Convert.GetTypeCode () a accéléré considérablement les performances, environ 25%, VS Enum.Parse () qui était 10 fois plus lent.


Je sais que ce post est vieux , mais IF en utilisant la méthode ENUM TypeCode, plus facile (et probablement le moins cher) serait quelque chose comme ceci:

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

Étant donné la définition d'énumération suivante pour 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
}

Je ne l'ai pas testé à fond, mais pour les types numériques de base C #, cela semble le couvrir. Cependant, comme @JonSkeet l'a mentionné, cette énumération n'est pas mise à jour pour les types supplémentaires ajoutés à .NET ultérieurement.

Hector Bas
la source
-1

Oups! J'ai mal lu la question! Personnellement, je roulerais avec Skeet .


hrm, ressemble à ce que vous voulez DoSomethingsur Typevos données. Ce que vous pourriez faire est ce qui suit

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;
    }
}
johnny g
la source