C #: come determinare se un tipo è un numero


105

C'è un modo per determinare se un determinato tipo .Net è un numero? Ad esempio: System.UInt32/UInt16/Doublesono tutti numeri. Voglio evitare un lungo switch-case su Type.FullName.


4
Dupe di molti, molti, molti. Perché non è stato ancora chiuso?
Noldorin

2
Duplicato di stackoverflow.com/questions/1130698 e molto vicino ad altri.
Henk Holterman

Risposte:


110

Prova questo:

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

I tipi primitivi sono Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, Char, Double e Single.

Prendendo un po 'oltre la soluzione di 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;
  }
}

Uso:

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

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

2
Quindi il decimaltipo non è numerico?
LukeH

2
@Xaero: non ho dubbi che decimal sia numerico. Solo perché non è una primitiva non significa che non sia numerica. Il tuo codice deve tenere conto di questo.
LukeH

2
Questo dovrebbe essere riprogettato per i nuovi tipi numerici in .NET 4.0 che non hanno codici di tipo.
Jon Skeet,

7
Come puoi sottovalutarmi su una risposta basata sulla tecnologia attuale. Forse in .NET 62, int verrà rimosso: hai intenzione di downvote tutte le risposte con int?
Philip Wallace,

1
@DiskJunky Scusa, amico. È successo quasi tre anni fa e non ricordo quale fosse il loro contenuto.
kdbanman

93

Non usare un interruttore, usa solo un set:

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

EDIT: un vantaggio di questo rispetto all'utilizzo di un codice di tipo è che quando vengono introdotti nuovi tipi numerici in .NET (ad esempio BigInteger e Complex ) è facile da regolare, mentre quei tipi non riceveranno un codice di tipo.


4
e come useresti HashSet?
RvdK

8
NumericTypes.Contains (qualunque cosa)?
mqp

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

Avrei pensato che il compilatore avrebbe eseguito una conversione implicita dell'istruzione switch in hashset.
Rolf Kristensen

6
@RolfKristensen: Beh, switchsemplicemente non funziona Type, quindi non puoi. Puoi accenderlo TypeCodeovviamente, ma è una questione diversa.
Jon Skeet

69

Nessuna delle soluzioni prende in considerazione Nullable.

Ho modificato un po 'la soluzione di 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));
    }

So che potrei semplicemente aggiungere il nullables stesso al mio HashSet. Ma questa soluzione evita il pericolo di dimenticare di aggiungere uno specifico Nullable alla tua lista.

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

2
Un tipo nullable è davvero numerico? Null non è un numero, per quanto ne so.
IllidanS4 vuole che Monica torni

2
Dipende da cosa vuoi ottenere. Nel mio caso dovevo includere anche nullables. Ma potrei anche pensare a situazioni in cui questo non è un comportamento desiderato.
Jürgen Steinblock

Buona! Per trattare un numero nullable come numero è molto utile nella convalida dell'input dell'interfaccia utente.
guogangj

1
@ IllidanS4 Il controllo è sul Tipo, non sul valore. Nella maggior parte dei casi, i tipi numerici nullable devono essere trattati come numerici. Ovviamente se il controllo era su value e value è nullo, allora sì, non dovrebbe essere considerato numerico.
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 sull'ottimizzazione rimossa (vedi commenti enzi) E se vuoi davvero ottimizzarla (perdendo leggibilità e un po 'di sicurezza ...):

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
So che questa risposta è vecchia, ma recentemente mi sono imbattuto in un tale interruttore: non utilizzare l'ottimizzazione suggerita! Ho esaminato il codice IL generato da tale interruttore e ho notato che il compilatore applica già l'ottimizzazione (in IL 5 viene sottratto dal codice del tipo e quindi i valori da 0 a 10 sono considerati veri). Quindi l'interruttore dovrebbe essere usato perché è più leggibile, più sicuro e altrettanto veloce.
enzi

1
Se in realtà vuoi ottimizzarlo e non ti interessa la leggibilità, il codice ottimale sarebbe return unchecked((uint)Type.GetTypeCode(type) - 5u) <= 10u;quindi rimuovere il ramo introdotto da &&.
AnorZaken

14

Fondamentalmente la soluzione di Skeet ma puoi riutilizzarla con i tipi nullable come segue:

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

Approccio basato sulla proposta di Philip , migliorato con il controllo del tipo interno di SFun28 per i Nullabletipi:

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

Perchè questo? Ho dovuto verificare se un dato Type typeè di tipo numerico e non se un arbitrario object oè numerico.


4

Con C # 7 questo metodo mi offre prestazioni migliori rispetto all'accensione del case 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;

I test sono i seguenti:

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

Puoi usare Type.IsPrimitive e quindi ordinare i tipi Booleane Char, qualcosa del genere:

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

EDIT : Si consiglia di escludere le IntPtre UIntPtrtipi così, se non si considerano loro di essere numerico.


1
Quindi il decimaltipo non è numerico?
LukeH

Ops ... beh, sembra che la soluzione di Guillaume sia la migliore dopo tutto.
Konamiman,

3

Estensione di tipo con supporto di tipo null.

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

Risposta breve: No.

Risposta più lunga: no.

Il fatto è che molti tipi diversi in C # possono contenere dati numerici. A meno che tu non sappia cosa aspettarti (Int, Double, ecc.), Devi usare l'istruzione case "long".


1

Anche questo potrebbe funzionare. Tuttavia, potresti voler seguire con un Type.Parse per lanciarlo nel modo desiderato in seguito.

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

1

Modificati di skeet e la soluzione di arviman utilizzando 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)
};

Seguito da:

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

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

    return IsNumeric;
}

Utilizzo per (T item):

if ( item.IsNumeric() ) {}

null restituisce false.


1

Il cambio è un po 'lento, perché ogni volta che i metodi nella situazione peggiore passeranno attraverso tutti i tipi. Penso che usare Dictonary sia più bello, in questa situazione avrai 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

Prova il pacchetto nuget TypeSupport per C #. Supporta il rilevamento di tutti i tipi numerici (tra molte altre funzionalità):

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

Non conoscevo questo pacchetto. Sembra essere un salvatore di vita in molti casi evitare di scrivere un nostro codice per il tipo di operazioni richieste dall'OP. Grazie !
AFract

0

Sfortunatamente questi tipi non hanno molto in comune a parte il fatto che sono tutti tipi di valore. Ma per evitare un lungo caso di commutazione potresti semplicemente definire un elenco di sola lettura con tutti questi tipi e quindi controllare se il tipo dato è all'interno dell'elenco.


0

Sono tutti tipi di valore (tranne bool e forse enum). Quindi potresti semplicemente usare:

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

1
Questo restituirà vero per qualsiasi definito dall'utente struct... Non penso che sia quello che vuoi.
Dan Tao

1
Hai ragione. Anche i tipi numerici incorporati sono strutture. Quindi è meglio andare con il confronto primitivo allora.
MandoMando

0

MODIFICARE: Bene, ho modificato il codice qui sotto per essere più performante e poi ho eseguito i test pubblicati da @Hugo contro di esso. Le velocità sono all'incirca alla pari con l'IF di @ Hugo usando l'ultimo elemento nella sua sequenza (Decimale). Tuttavia, se si utilizza il primo elemento "byte", il gioco è fatto, ma chiaramente l'ordine è importante quando si tratta di prestazioni. Sebbene l'utilizzo del codice riportato di seguito sia più facile da scrivere e più coerente sul suo costo, non è tuttavia gestibile o a prova di futuro.

Sembra che il passaggio da Type.GetTypeCode () a Convert.GetTypeCode () abbia accelerato drasticamente le prestazioni, circa il 25%, VS Enum.Parse () che era circa 10 volte più lento.


So che questo post è vecchio ma SE si utilizza il metodo enumerazione TypeCode, il più semplice (e probabilmente il più economico) sarebbe qualcosa del genere:

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

Data la seguente definizione di enum per 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
}

Non l'ho testato a fondo, ma per i tipi numerici C # di base, questo sembrerebbe coprirlo. Tuttavia, come menzionato da @JonSkeet, questa enumerazione non viene aggiornata per altri tipi aggiunti a .NET in futuro.


-1

oops! Ho letto male la domanda! Personalmente, rotolerei con Skeet's .


hrm, sembra che tu voglia DoSomethingsui Typetuoi dati. Quello che potresti fare è il seguente

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;
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.