C # - wie man bestimmt, ob ein Typ eine Zahl ist

105

Gibt es eine Möglichkeit festzustellen, ob ein bestimmter .NET-Typ eine Zahl ist oder nicht? Zum Beispiel: System.UInt32/UInt16/Doublesind alle Zahlen. Ich möchte einen langen Schaltkasten am vermeiden Type.FullName.

Adi Barda
quelle
4
Betrüger von vielen, vielen, vielen. Warum wurde dies noch nicht geschlossen?
Noldorin
2
Duplikat von stackoverflow.com/questions/1130698 und sehr nah an einigen anderen.
Henk Holterman

Antworten:

110

Versuche dies:

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

Die primitiven Typen sind Boolesch, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, Char, Double und Single.

Unter Guillaume-Lösung ein wenig weiter:

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

Verwendung:

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

string s = "Hello World";
s.IsNumericType(); // False
Philip Wallace
quelle
2
Der decimalTyp ist also nicht numerisch?
LukeH
2
@ Xaero: Ich habe keinen Zweifel, dass decimal das numerisch ist. Nur weil es kein Primitiv ist, heißt das nicht, dass es nicht numerisch ist. Ihr Code muss dies berücksichtigen.
LukeH
2
Dies müsste für die neuen numerischen Typen in .NET 4.0 ohne Typcodes überarbeitet werden.
Jon Skeet
7
Wie können Sie mich auf eine Antwort abstimmen, die auf der aktuellen Technologie basiert? Vielleicht wird in .NET 62 int entfernt - werden Sie alle Antworten mit int herabstimmen?
Philip Wallace
1
@ DiskJunky Sorry, Freund. Das war vor fast drei Jahren und ich erinnere mich nicht, was ihr Inhalt war.
Kdbanman
93

Verwenden Sie keinen Schalter - verwenden Sie einfach ein Set:

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

BEARBEITEN: Ein Vorteil gegenüber der Verwendung eines Typcodes besteht darin, dass bei der Einführung neuer numerischer Typen in .NET (z. B. BigInteger und Complex ) die Anpassung einfach ist - während diese Typen keinen Typcode erhalten.

Jon Skeet
quelle
4
und wie würden Sie das HashSet verwenden?
RvdK
8
NumericTypes.Contains (was auch immer)?
mqp
3
bool isANumber = NumericTypes.Contains (classInstance.GetType ());
Yuriy Faktorovich
Hätte gedacht, der Compiler würde eine implizite Konvertierung der switch-Anweisung in hashset durchführen.
Rolf Kristensen
6
@RolfKristensen: Nun switch, funktioniert einfach nicht Type, also kannst du nicht. Sie können TypeCodenatürlich einschalten , aber das ist eine andere Sache.
Jon Skeet
69

Keine der Lösungen berücksichtigt Nullable.

Ich habe Jon Skeets Lösung ein wenig modifiziert:

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

Ich weiß, ich könnte einfach die Nullables selbst zu meinem HashSet hinzufügen. Diese Lösung vermeidet jedoch die Gefahr, dass Sie vergessen, Ihrer Liste eine bestimmte Nullable hinzuzufügen.

    private static HashSet<Type> NumericTypes = new HashSet<Type>
    {
        typeof(int),
        typeof(int?),
        ...
    };
Jürgen Steinblock
quelle
2
Ist ein nullbarer Typ wirklich numerisch? Null ist meines Wissens keine Zahl.
IllidanS4 will Monica
2
Das hängt davon ab, was Sie erreichen wollen. In meinem Fall musste ich auch Nullables einschließen. Ich könnte mir aber auch Situationen vorstellen, in denen dies kein gewünschtes Verhalten ist.
Jürgen Steinblock
Gut! Die Behandlung einer nullbaren Zahl als Zahl ist bei der Validierung von UI-Eingaben sehr nützlich.
Guogangj
1
@ IllidanS4 Die Prüfung ist auf Typ, nicht der Wert. In den meisten Fällen sollten nullfähige numerische Typen als numerisch behandelt werden. Wenn die Prüfung auf Wert war und der Wert null ist, sollte sie natürlich nicht als numerisch betrachtet werden.
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;
  }
}

Hinweis zur Optimierung entfernt (siehe enzi-Kommentare) Und wenn Sie es wirklich optimieren möchten (Lesbarkeit und Sicherheit verlieren ...):

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
quelle
13
Ich weiß, dass diese Antwort alt ist, aber ich bin kürzlich auf einen solchen Schalter gestoßen: Verwenden Sie nicht die vorgeschlagene Optimierung! Ich habe mir den IL-Code angesehen, der von einem solchen Schalter generiert wurde, und festgestellt, dass der Compiler die Optimierung bereits anwendet (in IL 5 wird vom Typcode subtrahiert und dann werden Werte von 0 bis 10 als wahr betrachtet). Daher sollte der Schalter verwendet werden, um besser lesbar, sicherer und genauso schnell zu sein.
Enzi
1
Wenn Sie es tatsächlich optimieren möchten und sich nicht um die Lesbarkeit kümmern möchten, würde der optimale Code return unchecked((uint)Type.GetTypeCode(type) - 5u) <= 10u;den von eingeführten Zweig entfernen &&.
AnorZaken
14

Grundsätzlich Skeets Lösung, aber Sie können sie mit Nullable-Typen wie folgt wiederverwenden:

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
quelle
9

Ansatz basierend auf Philipps Vorschlag , erweitert durch die innere Typprüfung von SFun28 für NullableTypen:

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

Warum das? Ich musste überprüfen, ob ein gegebener Type typeein numerischer Typ ist und nicht, ob ein beliebiger numerischer Typ object oist.

Cimnin
quelle
4

Mit C # 7 bietet mir diese Methode eine bessere Leistung als das Einschalten des Gehäuses TypeCodeund 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;

Tests sind folgende:

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
quelle
3

Sie könnten Type.IsPrimitive verwenden und dann die Booleanund Char-Typen aussortieren, ungefähr so:

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

BEARBEITEN : Möglicherweise möchten Sie auch die Typen IntPtrund ausschließen UIntPtr, wenn Sie sie nicht als numerisch betrachten.

Konamiman
quelle
1
Der decimalTyp ist also nicht numerisch?
LukeH
Hoppla ... nun, es scheint, dass Guillaumes Lösung doch die beste ist.
Konamiman
3

Typerweiterung mit Unterstützung vom Typ 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;
        }
    }
Mateusz Kraska
quelle
1

Kurze Antwort: Nein.

Längere Antwort: Nein.

Tatsache ist, dass viele verschiedene Typen in C # numerische Daten enthalten können. Sofern Sie nicht wissen, was Sie erwartet (Int, Double usw.), müssen Sie die "long" case-Anweisung verwenden.

Craig
quelle
1

Dies kann auch funktionieren. Möglicherweise möchten Sie jedoch einen Typ.Parse verwenden, um ihn anschließend so zu übertragen, wie Sie es möchten.

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

Geändert Skeet und arviman-Lösung verwendet Generics, Reflectionund 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)
};

Gefolgt von:

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

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

    return IsNumeric;
}

Verwendung für (T item):

if ( item.IsNumeric() ) {}

null gibt false zurück.

Hauskatze
quelle
1

Der Wechsel ist etwas langsam, da jedes Mal, wenn Methoden in der schlimmsten Situation alle Arten durchlaufen. Ich denke, Dictonary zu verwenden ist schöner, in dieser Situation haben Sie 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
quelle
1

Probieren Sie das TypeSupport- Nuget-Paket für C # aus. Es unterstützt die Erkennung aller numerischen Typen (neben vielen anderen Funktionen):

var extendedType = typeof(int).GetExtendedType();
Assert.IsTrue(extendedType.IsNumericType);
Michael Brown
quelle
Ich kannte dieses Paket nicht. Scheint in vielen Fällen ein Lebensretter zu sein, um zu vermeiden, unseren eigenen Code für die vom OP angeforderten Operationen zu schreiben. Vielen Dank !
Nach dem
0

Leider haben diese Typen nicht viel gemeinsam, außer dass sie alle Werttypen sind. Um einen langen Wechsel zu vermeiden, können Sie einfach eine schreibgeschützte Liste mit all diesen Typen definieren und dann überprüfen, ob der angegebene Typ in der Liste enthalten ist.

Darin Dimitrov
quelle
0

Sie sind alle Werttypen (außer bool und möglicherweise enum). Sie können also einfach Folgendes verwenden:

bool IsNumberic(object o)
{
    return (o is System.ValueType && !(o is System.Boolean) && !(o is System.Enum))
}
MandoMando
quelle
1
Dies wird für alle benutzerdefinierten Werte "true struct" zurückgeben. Ich glaube nicht, dass Sie dies wünschen.
Dan Tao
1
Du hast Recht. Eingebaute numerische Typen sind ebenfalls Strukturen. Gehen Sie also besser zum primitiven Vergleich.
MandoMando
0

EDIT: Nun, ich habe den folgenden Code geändert, um eine bessere Leistung zu erzielen, und dann die von @Hugo veröffentlichten Tests dagegen ausgeführt. Die Geschwindigkeiten sind ungefähr gleich hoch wie bei @ Hugos IF, wenn das letzte Element in seiner Sequenz (Dezimal) verwendet wird. Wenn Sie jedoch das erste Element "Byte" verwenden, nimmt er den Kuchen, aber die Reihenfolge ist eindeutig wichtig, wenn es um die Leistung geht. Die Verwendung des folgenden Codes ist zwar einfacher zu schreiben und hinsichtlich der Kosten konsistenter, jedoch nicht wartbar oder zukunftssicher.

Der Wechsel von Type.GetTypeCode () zu Convert.GetTypeCode () hat die Leistung drastisch beschleunigt, etwa 25% gegenüber VS Enum.Parse (), was etwa zehnmal langsamer war.


Ich weiß , dass dieser Beitrag ist alt, aber IF die Typecode - Enumeration - Methode, einfachste (und wahrscheinlich das billigste) würde wie folgt sein:

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

Angesichts der folgenden Aufzählungsdefinition für 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
}

Ich habe es nicht gründlich getestet, aber für grundlegende C # -Numeriktypen scheint dies zu reichen. Wie bereits von @JonSkeet erwähnt, wird diese Aufzählung jedoch nicht für zusätzliche Typen aktualisiert, die später zu .NET hinzugefügt werden.

Hector Bas
quelle
-1

Hoppla! Die Frage falsch verstanden! Persönlich würde mit Skeet rollen .


HRM, klingt wie Sie wollen DoSomethingauf TypeIhre Daten. Was Sie tun könnten, ist das Folgende

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
quelle